def test_norm_int(): # simps(y, x) == 2.0 x = np.linspace(0, np.pi, 100) y = np.sin(x) for scale in [True, False]: yy = norm_int(y, x, area=10.0, scale=scale) assert np.allclose(simps(yy, x), 10.0)
def test_norm_int(): # simps(y, x) == 2.0 x = np.linspace(0, np.pi, 100) y = np.sin(x) for scale in [True, False]: yy = norm_int(y, x, area=10.0, scale=scale) assert np.allclose(simps(yy,x), 10.0)
def cut_norm(full_y, dt, area=1.0): """Cut out an FFT spectrum from scipy.fftpack.fft() (or numpy.fft.fft()) result and normalize the integral `int(f) y(f) df = area`. full_y : 1d array Result of fft(...) dt : time step area : integral area """ full_faxis = np.fft.fftfreq(full_y.shape[0], dt) split_idx = full_faxis.shape[0] / 2 y_out = full_y[:split_idx] faxis = full_faxis[:split_idx] return faxis, num.norm_int(y_out, faxis, area=area)
def cut_norm(full_y, dt, area=1.0): """Cut out an FFT spectrum from scipy.fftpack.fft() (or numpy.fft.fft()) result and normalize the integral `int(f) y(f) df = area`. full_y : 1d array Result of fft(...) dt : time step area : integral area """ full_faxis = np.fft.fftfreq(full_y.shape[0], dt) split_idx = full_faxis.shape[0]/2 y_out = full_y[:split_idx] faxis = full_faxis[:split_idx] return faxis, num.norm_int(y_out, faxis, area=area)
def pdos(vel, dt=1.0, m=None, full_out=False, area=1.0, window=True, npad=None, tonext=False, mirr=False, method='direct'): """Phonon DOS by FFT of the VACF or direct FFT of atomic velocities. Integral area is normalized to `area`. It is possible (and recommended) to zero-padd the velocities (see `npad`). Parameters ---------- vel : 3d array (nstep, natoms, 3) atomic velocities dt : time step m : 1d array (natoms,), atomic mass array, if None then mass=1.0 for all atoms is used full_out : bool area : float normalize area under frequency-PDOS curve to this value window : bool use Welch windowing on data before FFT (reduces leaking effect, recommended) npad : {None, int} method='direct' only: Length of zero padding along `axis`. `npad=None` = no padding, `npad > 0` = pad by a length of ``(nstep-1)*npad``. `npad > 5` usually results in sufficient interpolation. tonext : bool method='direct' only: Pad `vel` with zeros along `axis` up to the next power of two after the array length determined by `npad`. This gives you speed, but variable (better) frequency resolution. mirr : bool method='vacf' only: mirror one-sided VACF at t=0 before fft Returns ------- if full_out = False | ``(faxis, pdos)`` | faxis : 1d array [1/unit(dt)] | pdos : 1d array, the phonon DOS, normalized to `area` if full_out = True | if method == 'direct': | ``(faxis, pdos, (full_faxis, full_pdos, split_idx))`` | if method == 'vavcf': | ``(faxis, pdos, (full_faxis, full_pdos, split_idx, vacf, fft_vacf))`` | fft_vacf : 1d complex array, result of fft(vacf) or fft(mirror(vacf)) | vacf : 1d array, the VACF Examples -------- >>> from pwtools.constants import fs,rcm_to_Hz >>> tr = Trajectory(...) >>> # freq in [Hz] if timestep in [s] >>> freq,dos = pdos(tr.velocity, m=tr.mass, dt=tr.timestep*fs, >>> method='direct', npad=1) >>> # frequency in [1/cm] >>> plot(freq/rcm_to_Hz, dos) Notes ----- padding (only method='direct'): With `npad` we pad the velocities `vel` with ``npad*(nstep-1)`` zeros along `axis` (the time axis) before FFT b/c the signal is not periodic. For `npad=1`, this gives us the exact same spectrum and frequency resolution as with ``pdos(..., method='vacf',mirr=True)`` b/c the array to be fft'ed has length ``2*nstep-1`` along the time axis in both cases (remember that the array length = length of the time axis influences the freq. resolution). FFT is only fast for arrays with length = a power of two. Therefore, you may get very different fft speeds depending on whether ``2*nstep-1`` is a power of two or not (in most cases it won't). Try using `tonext` but remember that you get another (better) frequency resolution. References ---------- [1] Phys Rev B 47(9) 4863, 1993 See Also -------- :func:`pwtools.signal.fftsample` :func:`pwtools.signal.acorr` :func:`direct_pdos` :func:`vacf_pdos` """ mass = m # assume vel.shape = (nstep,natoms,3) axis = 0 assert vel.shape[-1] == 3 if mass is not None: assert len(mass) == vel.shape[1], "len(mass) != vel.shape[1]" # define here b/c may be used twice below mass_bc = mass[None, :, None] if window: sl = [None] * vel.ndim sl[axis] = slice(None) # ':' vel2 = vel * (welch(vel.shape[axis])[sl]) else: vel2 = vel # handle options which are mutually exclusive if method == 'vacf': assert npad in [0, None], "use npad={0,None} for method='vacf'" # padding if npad is not None: nadd = (vel2.shape[axis] - 1) * npad if tonext: vel2 = pad_zeros(vel2, tonext=True, tonext_min=vel2.shape[axis] + nadd, axis=axis) else: vel2 = pad_zeros(vel2, tonext=False, nadd=nadd, axis=axis) if method == 'direct': full_fft_vel = np.abs(fft(vel2, axis=axis))**2.0 full_faxis = np.fft.fftfreq(vel2.shape[axis], dt) split_idx = len(full_faxis) // 2 faxis = full_faxis[:split_idx] # First split the array, then multiply by `mass` and average. If # full_out, then we need full_fft_vel below, so copy before slicing. arr = full_fft_vel.copy() if full_out else full_fft_vel fft_vel = num.slicetake(arr, slice(0, split_idx), axis=axis, copy=False) if mass is not None: fft_vel *= mass_bc # average remaining axes, summing is enough b/c normalization is done below # sums: (nstep, natoms, 3) -> (nstep, natoms) -> (nstep,) pdos = num.sum(fft_vel, axis=axis, keepdims=True) default_out = (faxis, num.norm_int(pdos, faxis, area=area)) if full_out: # have to re-calculate this here b/c we never calculate the full_pdos # normally if mass is not None: full_fft_vel *= mass_bc full_pdos = num.sum(full_fft_vel, axis=axis, keepdims=True) extra_out = (full_faxis, full_pdos, split_idx) return default_out + extra_out else: return default_out elif method == 'vacf': vacf = fvacf(vel2, m=mass) if mirr: fft_vacf = fft(mirror(vacf)) else: fft_vacf = fft(vacf) full_faxis = np.fft.fftfreq(fft_vacf.shape[axis], dt) full_pdos = np.abs(fft_vacf) split_idx = len(full_faxis) // 2 faxis = full_faxis[:split_idx] pdos = full_pdos[:split_idx] default_out = (faxis, num.norm_int(pdos, faxis, area=area)) extra_out = (full_faxis, full_pdos, split_idx, vacf, fft_vacf) if full_out: return default_out + extra_out else: return default_out
fourier_out_data_fn = pj(fourier_dir, 'fourier_out_data_1d.txt') fourier_in_fn = pj(fourier_dir, 'fourier_1d.in') fourier_out_fn = pj(fourier_dir, 'fourier_1d.log') fourier_in_txt = '%s\n%s\n%e\n%e\n%e\n%i' % ( fourier_in_data_fn, fourier_out_data_fn, dt / constants.th, 0, fmax * fmax_extend_fac / (constants.c0 * 100), 1) common.file_write(fourier_in_fn, fourier_in_txt) # In order to make picky gfortrans happy, we need to use savetxt(..., # fmt="%g") such that the first column is an integer (1 instead of # 1.0000e+00). np.savetxt(fourier_in_data_fn, fourier_in_data, fmt='%g') common.system("%s < %s > %s" % (fourier_exe, fourier_in_fn, fourier_out_fn)) fourier_out_data = np.loadtxt(fourier_out_data_fn) f3 = fourier_out_data[:, 0] * (constants.c0 * 100) # 1/cm -> Hz y3n = num.norm_int(fourier_out_data[:, 1], f3) f1, y1n = cut_norm(y1, dt) f2, y2n = cut_norm(y2, dt) figs = [] axs = [] figs.append(plt.figure()) axs.append(figs[-1].add_subplot(111)) axs[-1].set_title('1d arr') axs[-1].plot(f1, y1n, label='1d |fft(arr)|^2, direct') axs[-1].plot(f2, y2n, label='1d |fft(acorr(arr))|, vacf') if use_fourier: axs[-1].plot(f3, y3n, label='1d fourier.x') axs[-1].legend()
fourier_out_fn = pj(fourier_dir, 'fourier_1d.log') fourier_in_txt = '%s\n%s\n%e\n%e\n%e\n%i' %(fourier_in_data_fn, fourier_out_data_fn, dt/constants.th, 0, fmax*fmax_extend_fac/(constants.c0*100), 1) common.file_write(fourier_in_fn, fourier_in_txt) # In order to make picky gfortrans happy, we need to use savetxt(..., # fmt="%g") such that the first column is an integer (1 instead of # 1.0000e+00). np.savetxt(fourier_in_data_fn, fourier_in_data, fmt='%g') common.system("%s < %s > %s" %(fourier_exe, fourier_in_fn, fourier_out_fn)) fourier_out_data = np.loadtxt(fourier_out_data_fn) f3 = fourier_out_data[:,0]*(constants.c0*100) # 1/cm -> Hz y3n = num.norm_int(fourier_out_data[:,1], f3) f1, y1n = cut_norm(y1, dt) f2, y2n = cut_norm(y2, dt) figs = [] axs = [] figs.append(plt.figure()) axs.append(figs[-1].add_subplot(111)) axs[-1].set_title('1d arr') axs[-1].plot(f1, y1n, label='1d |fft(arr)|^2, direct') axs[-1].plot(f2, y2n, label='1d |fft(acorr(arr))|, vacf') if use_fourier: axs[-1].plot(f3, y3n, label='1d fourier.x') axs[-1].legend()