1 """Contains functions for doing the inverse and forward normal mode transforms.
3 Copyright (C) 2013, Joshua More and Michele Ceriotti
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http.//www.gnu.org/licenses/>.
20 nm_trans: Uses matrix multiplication to do normal mode transformations.
21 nm_rescale: Uses matrix multiplication to do ring polymer contraction
23 nm_fft: Uses fast-Fourier transforms to do normal modes transformations.
26 mk_nm_matrix: Makes a matrix to transform between the normal mode and bead
28 mk_rs_matrix: Makes a matrix to transform between one number of beads and
29 another. Higher normal modes in the case of an expansion are set to zero.
32 __all__
= ['nm_trans', 'nm_rescale', 'nm_fft']
35 from ipi
.utils
.messages
import verbosity
, info
37 def mk_nm_matrix(nbeads
):
38 """Gets the matrix that transforms from the bead representation
39 to the normal mode representation.
41 If we return from this function a matrix C, then we transform between the
42 bead and normal mode representation using q_nm = C . q_b, q_b = C.T . q_nm
45 nbeads: The number of beads.
48 b2nm
= np
.zeros((nbeads
,nbeads
))
49 b2nm
[0,:] = np
.sqrt(1.0)
50 for j
in range(nbeads
):
51 for i
in range(1, nbeads
/2+1):
52 b2nm
[i
,j
] = np
.sqrt(2.0)*np
.cos(2*np
.pi
*j
*i
/float(nbeads
))
53 for i
in range(nbeads
/2+1, nbeads
):
54 b2nm
[i
,j
] = np
.sqrt(2.0)*np
.sin(2*np
.pi
*j
*i
/float(nbeads
))
56 b2nm
[nbeads
/2,0:nbeads
:2] = 1.0
57 b2nm
[nbeads
/2,1:nbeads
:2] = -1.0
58 return b2nm
/np
.sqrt(nbeads
)
60 def mk_rs_matrix(nb1
, nb2
):
61 """Gets the matrix that transforms a path with nb1 beads into one with
64 If we return from this function a matrix T, then we transform between the
65 system with nb1 bead and the system of nb2 beads using q_2 = T . q_1
68 nb1: The initial number of beads.
69 nb2: The final number of beads.
73 return np
.identity(nb1
,float)
75 b1_nm
= mk_nm_matrix(nb1
)
76 nm_b2
= mk_nm_matrix(nb2
).T
78 #builds the "reduction" matrix that picks the normal modes we want to keep
79 b1_b2
= np
.zeros((nb2
, nb1
), float)
81 for i
in range(1, nb2
/2+1):
83 b1_b2
[nb2
-i
, nb1
-i
] = 1.0
85 #if we are contracting down to an even number of beads, then we have to
86 #pick just one of the last degenerate modes to match onto the single
87 #stiffest mode in the new path
88 b1_b2
[nb2
/2, nb1
-nb2
/2] = 0.0
90 rs_b1_b2
= np
.dot(nm_b2
, np
.dot(b1_b2
, b1_nm
))
91 return rs_b1_b2
*np
.sqrt(float(nb2
)/float(nb1
))
93 return mk_rs_matrix(nb2
, nb1
).T
*(float(nb2
)/float(nb1
))
97 """Helper class to perform beads <--> normal modes transformation.
100 _b2nm: The matrix to transform between the bead and normal mode
102 _nm2b: The matrix to transform between the normal mode and bead
106 def __init__(self
, nbeads
):
107 """Initializes nm_trans.
110 nbeads: The number of beads.
113 self
._b
2nm
= mk_nm_matrix(nbeads
)
114 self
._nm
2b
= self
._b
2nm
.T
117 """Transforms a matrix to the normal mode representation.
120 q: A matrix with nbeads rows, in the bead representation.
123 return np
.dot(self
._b
2nm
,q
)
126 """Transforms a matrix to the bead representation.
129 q: A matrix with nbeads rows, in the normal mode representation.
132 return np
.dot(self
._nm
2b
,q
)
136 """Helper class to rescale a ring polymer between different number of beads.
139 _b1tob2: The matrix to transform between a ring polymer with 'nbeads1'
140 beads and another with 'nbeads2' beads.
141 _b2tob1: The matrix to transform between a ring polymer with 'nbeads2'
142 beads and another with 'nbeads1' beads.
145 def __init__(self
, nbeads1
, nbeads2
):
146 """Initializes nm_rescale.
149 nbeads1: The initial number of beads.
150 nbeads2: The rescaled number of beads.
153 self
._b
1tob
2 = mk_rs_matrix(nbeads1
,nbeads2
)
154 self
._b
2tob
1 = self
._b
1tob
2.T
*(float(nbeads1
)/float(nbeads2
))
157 """Transforms a matrix from one value of beads to another.
160 q: A matrix with nbeads1 rows, in the bead representation.
163 return np
.dot(self
._b
1tob
2,q
)
166 """Transforms a matrix from one value of beads to another.
169 q: A matrix with nbeads2 rows, in the bead representation.
172 return np
.dot(self
._b
2tob
1,q
)
177 """Helper class to perform beads <--> normal modes transformation
178 using Fast Fourier transforms.
181 fft: The fast-Fourier transform function to transform between the
182 bead and normal mode representations.
183 ifft: The inverse fast-Fourier transform function to transform
184 between the normal mode and bead representations.
185 qdummy: A matrix to hold a copy of the bead positions to transform
186 them to the normal mode representation.
187 qnmdummy: A matrix to hold a copy of the normal modes to transform
188 them to the bead representation.
189 nbeads: The number of beads.
190 natoms: The number of atoms.
193 def __init__(self
, nbeads
, natoms
):
194 """Initializes nm_trans.
197 nbeads: The number of beads.
198 natoms: The number of atoms.
205 info("Import of PyFFTW successful", verbosity
.medium
)
206 self
.qdummy
= pyfftw
.n_byte_align_empty((nbeads
, 3*natoms
), 16, 'float32')
207 self
.qnmdummy
= pyfftw
.n_byte_align_empty((nbeads
//2+1, 3*natoms
), 16, 'complex64')
208 self
.fft
= pyfftw
.FFTW(self
.qdummy
, self
.qnmdummy
, axes
=(0,), direction
='FFTW_FORWARD')
209 self
.ifft
= pyfftw
.FFTW(self
.qnmdummy
, self
.qdummy
, axes
=(0,), direction
='FFTW_BACKWARD')
210 except ImportError: #Uses standard numpy fft library if nothing better
212 info("Import of PyFFTW unsuccessful, using NumPy library instead", verbosity
.medium
)
213 self
.qdummy
= np
.zeros((nbeads
,3*natoms
), dtype
='float32')
214 self
.qnmdummy
= np
.zeros((nbeads
//2+1,3*natoms
), dtype
='complex64')
216 self
.qnmdummy
= np
.fft
.rfft(self
.qdummy
, axis
=0)
217 def dummy_ifft(self
):
218 self
.qdummy
= np
.fft
.irfft(self
.qnmdummy
, n
=self
.nbeads
, axis
=0)
219 self
.fft
= lambda: dummy_fft(self
)
220 self
.ifft
= lambda: dummy_ifft(self
)
223 """Transforms a matrix to the normal mode representation.
226 q: A matrix with nbeads rows and 3*natoms columns,
227 in the bead representation.
235 return self
.qnmdummy
.real
/np
.sqrt(self
.nbeads
)
237 nmodes
= self
.nbeads
/2
239 self
.qnmdummy
/= np
.sqrt(self
.nbeads
)
240 qnm
= np
.zeros(q
.shape
)
241 qnm
[0,:] = self
.qnmdummy
[0,:].real
243 if self
.nbeads
% 2 == 0:
244 self
.qnmdummy
[1:-1,:] *= np
.sqrt(2)
245 (qnm
[1:nmodes
,:], qnm
[self
.nbeads
:nmodes
:-1,:]) = (self
.qnmdummy
[1:-1,:].real
, self
.qnmdummy
[1:-1,:].imag
)
246 qnm
[nmodes
,:] = self
.qnmdummy
[nmodes
,:].real
248 self
.qnmdummy
[1:,:] *= np
.sqrt(2)
249 (qnm
[1:nmodes
+1,:], qnm
[self
.nbeads
:nmodes
:-1,:]) = (self
.qnmdummy
[1:,:].real
, self
.qnmdummy
[1:,:].imag
)
254 """Transforms a matrix to the bead representation.
257 qnm: A matrix with nbeads rows and 3*natoms columns,
258 in the normal mode representation.
264 self
.qnmdummy
[:] = qnm
266 return self
.qdummy
*np
.sqrt(self
.nbeads
)
268 nmodes
= self
.nbeads
/2
269 odd
= self
.nbeads
- 2*nmodes
# 0 if even, 1 if odd
271 qnm_complex
= np
.zeros((nmodes
+1, len(qnm
[0,:])), complex)
272 qnm_complex
[0,:] = qnm
[0,:]
274 (qnm_complex
[1:-1,:].real
, qnm_complex
[1:-1,:].imag
) = (qnm
[1:nmodes
,:], qnm
[self
.nbeads
:nmodes
:-1,:])
275 qnm_complex
[1:-1,:] /= np
.sqrt(2)
276 qnm_complex
[nmodes
,:] = qnm
[nmodes
,:]
278 (qnm_complex
[1:,:].real
, qnm_complex
[1:,:].imag
) = (qnm
[1:nmodes
+1,:], qnm
[self
.nbeads
:nmodes
:-1,:])
279 qnm_complex
[1:,:] /= np
.sqrt(2)
281 self
.qnmdummy
[:] = qnm_complex
283 return self
.qdummy
*np
.sqrt(self
.nbeads
)