"""Contains functions for doing the inverse and forward normal mode transforms. Copyright (C) 2013, Joshua More and Michele Ceriotti This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Classes: nm_trans: Uses matrix multiplication to do normal mode transformations. nm_rescale: Uses matrix multiplication to do ring polymer contraction or expansion. nm_fft: Uses fast-Fourier transforms to do normal modes transformations. Functions: mk_nm_matrix: Makes a matrix to transform between the normal mode and bead representations. mk_rs_matrix: Makes a matrix to transform between one number of beads and another. Higher normal modes in the case of an expansion are set to zero. """ __all__ = ['nm_trans', 'nm_rescale', 'nm_fft'] import numpy as np from ipi.utils.messages import verbosity, info def mk_nm_matrix(nbeads): """Gets the matrix that transforms from the bead representation to the normal mode representation. If we return from this function a matrix C, then we transform between the bead and normal mode representation using q_nm = C . q_b, q_b = C.T . q_nm Args: nbeads: The number of beads. """ b2nm = np.zeros((nbeads,nbeads)) b2nm[0,:] = np.sqrt(1.0) for j in range(nbeads): for i in range(1, nbeads/2+1): b2nm[i,j] = np.sqrt(2.0)*np.cos(2*np.pi*j*i/float(nbeads)) for i in range(nbeads/2+1, nbeads): b2nm[i,j] = np.sqrt(2.0)*np.sin(2*np.pi*j*i/float(nbeads)) if (nbeads%2) == 0: b2nm[nbeads/2,0:nbeads:2] = 1.0 b2nm[nbeads/2,1:nbeads:2] = -1.0 return b2nm/np.sqrt(nbeads) def mk_rs_matrix(nb1, nb2): """Gets the matrix that transforms a path with nb1 beads into one with nb2 beads. If we return from this function a matrix T, then we transform between the system with nb1 bead and the system of nb2 beads using q_2 = T . q_1 Args: nb1: The initial number of beads. nb2: The final number of beads. """ if (nb1 == nb2): return np.identity(nb1,float) elif (nb1 > nb2): b1_nm = mk_nm_matrix(nb1) nm_b2 = mk_nm_matrix(nb2).T #builds the "reduction" matrix that picks the normal modes we want to keep b1_b2 = np.zeros((nb2, nb1), float) b1_b2[0,0] = 1.0 for i in range(1, nb2/2+1): b1_b2[i,i] = 1.0 b1_b2[nb2-i, nb1-i] = 1.0 if (nb2 % 2 == 0): #if we are contracting down to an even number of beads, then we have to #pick just one of the last degenerate modes to match onto the single #stiffest mode in the new path b1_b2[nb2/2, nb1-nb2/2] = 0.0 rs_b1_b2 = np.dot(nm_b2, np.dot(b1_b2, b1_nm)) return rs_b1_b2*np.sqrt(float(nb2)/float(nb1)) else: return mk_rs_matrix(nb2, nb1).T*(float(nb2)/float(nb1)) class nm_trans: """Helper class to perform beads <--> normal modes transformation. Attributes: _b2nm: The matrix to transform between the bead and normal mode representations. _nm2b: The matrix to transform between the normal mode and bead representations. """ def __init__(self, nbeads): """Initializes nm_trans. Args: nbeads: The number of beads. """ self._b2nm = mk_nm_matrix(nbeads) self._nm2b = self._b2nm.T def b2nm(self, q): """Transforms a matrix to the normal mode representation. Args: q: A matrix with nbeads rows, in the bead representation. """ return np.dot(self._b2nm,q) def nm2b(self, q): """Transforms a matrix to the bead representation. Args: q: A matrix with nbeads rows, in the normal mode representation. """ return np.dot(self._nm2b,q) class nm_rescale: """Helper class to rescale a ring polymer between different number of beads. Attributes: _b1tob2: The matrix to transform between a ring polymer with 'nbeads1' beads and another with 'nbeads2' beads. _b2tob1: The matrix to transform between a ring polymer with 'nbeads2' beads and another with 'nbeads1' beads. """ def __init__(self, nbeads1, nbeads2): """Initializes nm_rescale. Args: nbeads1: The initial number of beads. nbeads2: The rescaled number of beads. """ self._b1tob2 = mk_rs_matrix(nbeads1,nbeads2) self._b2tob1 = self._b1tob2.T*(float(nbeads1)/float(nbeads2)) def b1tob2(self, q): """Transforms a matrix from one value of beads to another. Args: q: A matrix with nbeads1 rows, in the bead representation. """ return np.dot(self._b1tob2,q) def b2tob1(self, q): """Transforms a matrix from one value of beads to another. Args: q: A matrix with nbeads2 rows, in the bead representation. """ return np.dot(self._b2tob1,q) class nm_fft: """Helper class to perform beads <--> normal modes transformation using Fast Fourier transforms. Attributes: fft: The fast-Fourier transform function to transform between the bead and normal mode representations. ifft: The inverse fast-Fourier transform function to transform between the normal mode and bead representations. qdummy: A matrix to hold a copy of the bead positions to transform them to the normal mode representation. qnmdummy: A matrix to hold a copy of the normal modes to transform them to the bead representation. nbeads: The number of beads. natoms: The number of atoms. """ def __init__(self, nbeads, natoms): """Initializes nm_trans. Args: nbeads: The number of beads. natoms: The number of atoms. """ self.nbeads = nbeads self.natoms = natoms try: import pyfftw info("Import of PyFFTW successful", verbosity.medium) self.qdummy = pyfftw.n_byte_align_empty((nbeads, 3*natoms), 16, 'float32') self.qnmdummy = pyfftw.n_byte_align_empty((nbeads//2+1, 3*natoms), 16, 'complex64') self.fft = pyfftw.FFTW(self.qdummy, self.qnmdummy, axes=(0,), direction='FFTW_FORWARD') self.ifft = pyfftw.FFTW(self.qnmdummy, self.qdummy, axes=(0,), direction='FFTW_BACKWARD') except ImportError: #Uses standard numpy fft library if nothing better #is available info("Import of PyFFTW unsuccessful, using NumPy library instead", verbosity.medium) self.qdummy = np.zeros((nbeads,3*natoms), dtype='float32') self.qnmdummy = np.zeros((nbeads//2+1,3*natoms), dtype='complex64') def dummy_fft(self): self.qnmdummy = np.fft.rfft(self.qdummy, axis=0) def dummy_ifft(self): self.qdummy = np.fft.irfft(self.qnmdummy, n=self.nbeads, axis=0) self.fft = lambda: dummy_fft(self) self.ifft = lambda: dummy_ifft(self) def b2nm(self, q): """Transforms a matrix to the normal mode representation. Args: q: A matrix with nbeads rows and 3*natoms columns, in the bead representation. """ if self.nbeads == 1: return q self.qdummy[:] = q self.fft() if self.nbeads == 2: return self.qnmdummy.real/np.sqrt(self.nbeads) nmodes = self.nbeads/2 self.qnmdummy /= np.sqrt(self.nbeads) qnm = np.zeros(q.shape) qnm[0,:] = self.qnmdummy[0,:].real if self.nbeads % 2 == 0: self.qnmdummy[1:-1,:] *= np.sqrt(2) (qnm[1:nmodes,:], qnm[self.nbeads:nmodes:-1,:]) = (self.qnmdummy[1:-1,:].real, self.qnmdummy[1:-1,:].imag) qnm[nmodes,:] = self.qnmdummy[nmodes,:].real else: self.qnmdummy[1:,:] *= np.sqrt(2) (qnm[1:nmodes+1,:], qnm[self.nbeads:nmodes:-1,:]) = (self.qnmdummy[1:,:].real, self.qnmdummy[1:,:].imag) return qnm def nm2b(self, qnm): """Transforms a matrix to the bead representation. Args: qnm: A matrix with nbeads rows and 3*natoms columns, in the normal mode representation. """ if self.nbeads == 1: return qnm if self.nbeads == 2: self.qnmdummy[:] = qnm self.ifft() return self.qdummy*np.sqrt(self.nbeads) nmodes = self.nbeads/2 odd = self.nbeads - 2*nmodes # 0 if even, 1 if odd qnm_complex = np.zeros((nmodes+1, len(qnm[0,:])), complex) qnm_complex[0,:] = qnm[0,:] if not odd: (qnm_complex[1:-1,:].real, qnm_complex[1:-1,:].imag) = (qnm[1:nmodes,:], qnm[self.nbeads:nmodes:-1,:]) qnm_complex[1:-1,:] /= np.sqrt(2) qnm_complex[nmodes,:] = qnm[nmodes,:] else: (qnm_complex[1:,:].real, qnm_complex[1:,:].imag) = (qnm[1:nmodes+1,:], qnm[self.nbeads:nmodes:-1,:]) qnm_complex[1:,:] /= np.sqrt(2) self.qnmdummy[:] = qnm_complex self.ifft() return self.qdummy*np.sqrt(self.nbeads)