Octopus
phonon_modes.F90
Go to the documentation of this file.
1!! Copyright (C) 2024 - 2026 M. Lueders
2!!
3!! This Source Code Form is subject to the terms of the Mozilla Public
4!! License, v. 2.0. If a copy of the MPL was not distributed with this
5!! file, You can obtain one at https://mozilla.org/MPL/2.0/.
6!!
7#include "global.h"
8
10
12
13 use, intrinsic :: iso_fortran_env
15 use debug_oct_m
16 use global_oct_m
17 use io_oct_m
20 use parser_oct_m
24
25 implicit none
26
27 private
28 public :: &
30
31
36 !
37 type, extends(classical_modes_t) :: phonon_modes_t
38
41 real(real64), allocatable :: alpha(:)
42 real(real64), allocatable :: l_m(:)
43 real(real64), allocatable :: prefactor(:)
44
45 integer :: num_super
46
47 type(namespace_t) :: namespace
48
49 contains
50
51 procedure :: init => phonon_modes_init
52 procedure :: sample => phonon_modes_sample
53 procedure :: get_displacements => phonon_modes_get_displacements
54 final :: phonon_modes_finalize
55
56 end type phonon_modes_t
57
58contains
59
63 !
64 subroutine phonon_modes_init(this, namespace, dim_space, num_atoms, periodic, masses)
65 class(phonon_modes_t), intent(inout) :: this
66 type(namespace_t), intent(in) :: namespace
67 integer, intent(in) :: dim_space
68 integer, intent(in) :: num_atoms
69 logical, intent(in) :: periodic
70 real(real64), intent(in) :: masses(:)
71
72 character(len=256) :: filename, version_string
73 character(len=800) :: line
74 character(len=128) :: dummy
75 integer :: iunit, iatom, idim, i
76 integer :: imode, num_modes, file_num_modes, file_num_atoms, num_zero_modes, max_modes
77 logical :: eof
78
79 real(real64), parameter :: frequency_default_threshold = 1.0e-5_real64
80 real(real64), parameter :: mass_rel_tolerance = 1.0e-3_real64
81 real(real64) :: frequency_threshold
82
83 real(real64) :: eigenvecs(1:dim_space*num_atoms)
84 real(real64) :: file_masses(1:num_atoms)
85 real(real64) :: alpha, frequency
86
87
88 push_sub(phonon_modes_init)
89
90 this%namespace = namespace
91 this%dim = dim_space * num_atoms
92 num_modes = 0
93
94 !%Variable PhononModesFile
95 !%Type string
96 !%Section System
97 !%Description
98 !% Filename for the phonon modes file, as generated by the oct-phonopy-eigenmodes
99 !% utility shipped with Octopus. This file is in plain text with the following format (version 1.0;
100 !% lines starting with # are treated as comments and are ignored, except within the
101 !% mass and eigenvector value blocks):
102 !% <tt>
103 !% Version: 1.0
104 !% Nmodes: <integer> # Number of modes in the file
105 !% Natoms: <integer> # Number of atoms in the supercell
106 !% Np: <integer> # Number of primitive cells in the supercell, used to generate the modes
107 !% Masses: # followed by Natoms atomic masses (in AMU, free format)
108 !% <float> <float> ...
109 !% frequency: <float> # Angular frequency of first mode (in atomic units)
110 !% <float> <float> <float> # Normal mode eigenvector (for first atom)
111 !% <float> <float> <float> # Normal mode eigenvector (for second atom)
112 !% ... # repeated for all atoms in the supercell
113 !% alpha: <float> # amplitude weight of the mode: g_m^(-1/2), i.e.
114 !% # 1 for region A (2q = G) and finite systems, 1/sqrt(2) for region B.
115 !% # For finite systems this line is optional and defaults to 1.
116 !% frequency: <float> # Frequency of second mode
117 !% ...
118 !% </tt>
119 !% The order of atoms has to be the same as in the input (or geometry) file; the masses
120 !% are used to verify consistency between the phonon calculation and this run.
121 !%
122 !% Note: the full Brillouin zone can be split into 3 regions:
123 !% - region A: (time reversal invariant points): -q = q + G
124 !% - region B: -q + G falls in region C
125 !% - region C: -q + G falls in region B
126 !% Eigenvectors of regions B and C can be expressed as real and imaginary components of those of region B only.
127 !%End
128 call parse_variable(namespace, "PhononModesFile", "NONE", filename)
129
130 !%Variable PhononModesZeroThreshold
131 !%Type float
132 !%Default 1.0e-5
133 !%Section System
134 !%Description
135 !% Frequency threshold (in atomic units), below which phonon modes should be considered as zero frequency modes,
136 !% such as translations or rotations, and be ignored for the generation of initial displacements.
137 !% This default corresponds to 1e-5 Ha = 0.27 meV = 0.065 THz.
138 !%End
139 call parse_variable(namespace, "PhononModesZeroThreshold", frequency_default_threshold, frequency_threshold)
141 if (trim(filename) /= "NONE") then
143 iunit = io_open(filename, action="read")
144
145 call next_data_line(iunit, line, filename, namespace)
146 read(line, *) dummy, version_string
147 if (trim(dummy) /= 'Version:' .or. trim(version_string) /= '1.0') then
148 message(1) = "Unsupported phonon file format in "//trim(filename)//" (expected 'Version: 1.0')."
149 message(2) = "Regenerate the file with the current oct-phonopy-eigenmodes utility."
150 call messages_fatal(2, namespace=namespace)
151 end if
152
153 call next_data_line(iunit, line, filename, namespace)
154 read(line, *) dummy, file_num_modes
155 if (trim(dummy) /= 'Nmodes:') then
156 message(1) = "Phonon file ("//trim(filename)//") is ill formatted (expected: 'Nmodes:')."
157 call messages_fatal(1, namespace=namespace)
158 end if
160 call next_data_line(iunit, line, filename, namespace)
161 read(line, *) dummy, file_num_atoms
162 if (trim(dummy) /= 'Natoms:') then
163 message(1) = "Phonon file ("//trim(filename)//") is ill formatted (expected 'Natoms:')."
164 call messages_fatal(1, namespace=namespace)
165 end if
166 if (file_num_atoms /= num_atoms) then
167 write(message(1), '(A,I0,A,I0,A)') "The phonon file ("//trim(filename)//") was generated for ", &
168 file_num_atoms, " atoms, but this system has ", num_atoms, " atoms."
169 call messages_fatal(1, namespace=namespace)
170 end if
171
172 call next_data_line(iunit, line, filename, namespace)
173 read(line, *) dummy, this%num_super
174 if (trim(dummy) /= 'Np:') then
175 message(1) = "Phonon file ("//trim(filename)//") is ill formatted (expected 'Np:')."
176 call messages_fatal(1, namespace=namespace)
177 end if
178 if (this%num_super < 1) then
179 write(message(1), '(A,I0,A)') "The phonon file ("//trim(filename)//") declares Np = ", &
180 this%num_super, ", but Np must be at least 1."
181 call messages_fatal(1, namespace=namespace)
182 end if
183
184 call next_data_line(iunit, line, filename, namespace)
185 read(line, *) dummy
186 if (trim(dummy) /= 'Masses:') then
187 message(1) = "Phonon file ("//trim(filename)//") is ill formatted (expected 'Masses:')."
188 call messages_fatal(1, namespace=namespace)
189 end if
190 read(iunit, *) file_masses(:)
191 do iatom = 1, num_atoms
192 if (abs(file_masses(iatom) - masses(iatom)) > mass_rel_tolerance * masses(iatom)) then
193 write(message(1), '(A,I0,A)') "Mass mismatch between the phonon file ("//trim(filename)// &
194 ") and this run for atom ", iatom, ":"
195 write(message(2), '(A,F14.8,A,F14.8,A)') " file: ", file_masses(iatom), " AMU, system: ", &
196 masses(iatom), " AMU."
197 message(3) = "The phonon calculation and the MTEF run must use identical masses."
198 call messages_fatal(3, namespace=namespace)
199 end if
200 end do
201
202 safe_allocate(this%frequencies(1:file_num_modes))
203 safe_allocate(this%alpha(1:file_num_modes))
204 safe_allocate(this%eigenvectors(1:this%dim, file_num_modes))
205
206 num_zero_modes = 0
207
208 do imode=1, file_num_modes
209 call next_data_line(iunit, line, filename, namespace)
210 read(line, *) dummy, frequency
211 if(trim(dummy) /= 'frequency:') then
212 message(1) = "Phonon file is ill formatted. ("//trim(filename)//")"
213 call messages_fatal(1, namespace=namespace)
214 end if
215 read(iunit, *) eigenvecs(:)
216 if (periodic) then
217 call next_data_line(iunit, line, filename, namespace)
218 read(line, *) dummy, alpha
219 if(trim(dummy) /= 'alpha:') then
220 message(1) = "Phonon file is ill formatted. ("//trim(filename)//")"
221 call messages_fatal(1, namespace=namespace)
222 end if
223 else
224 ! For finite systems the alpha line is optional and defaults to 1
225 ! (all modes are single real modes, g_m = 1).
226 alpha = 1.0_real64
227 call next_data_line(iunit, line, filename, namespace, eof)
228 if (.not. eof) then
229 read(line, *) dummy
230 if (trim(dummy) == 'alpha:') then
231 read(line, *) dummy, alpha
232 else
233 ! Not an alpha line: push it back for the next mode.
234 backspace(iunit)
235 end if
236 end if
237 end if
238 if (frequency > frequency_threshold) then
239 num_modes = num_modes + 1
240 this%frequencies(num_modes) = frequency
241 this%eigenvectors(:, num_modes) = eigenvecs(:)
242 this%alpha(num_modes) = alpha
243 else
244 write(message(1), '(A,"Skip zero frequency mode. Omega = ",F15.5)') trim(filename), frequency
245 call messages_info(1, namespace=namespace)
246 num_zero_modes = num_zero_modes + 1
247 end if
248 end do
249
250 this%num_modes = num_modes
251 ! A periodic supercell has the full dim_space*num_atoms modes (of which only the dim_space
252 ! acoustic modes at Gamma are zero). For finite systems the bound excludes the translations;
253 ! we cannot simply also subtract the dim_space rotational modes, as molecular symmetries
254 ! might reduce that.
255 if (periodic) then
256 max_modes = dim_space*num_atoms
257 else
258 max_modes = dim_space*(num_atoms-1)
259 end if
260
261 if (this%num_modes + num_zero_modes > max_modes) then
262 message(1) = "The phonon file ("//trim(filename)//") contains too many non-zero frequency modes."
263 message(2) = "Most likely the zero frequency modes have not been identified. Try increasing PhononModesZeroThreshold."
264 message(3) = "Note that for molecules with rotational symmetry, this warning might be triggered wrongly."
265 call messages_warning(3, namespace=namespace)
266 end if
267
268 ! We can now already precalculate some other quantities, we will use later:
269
270 ! l_m = alpha_m * l~_m with l~_m = sqrt(1/(M_0*omega_m)) and M_0 = 1 AMU.
271 allocate(this%l_m(1:this%num_modes))
272 this%l_m = sqrt(1.0_real64/(unit_amu%factor * this%frequencies(1:this%num_modes))) * this%alpha(1:this%num_modes)
273
274 allocate(this%prefactor(1:(dim_space*num_atoms)))
275 i = 1
276 do iatom = 1, num_atoms
277 do idim=1, dim_space
278 this%prefactor(i) = 1.0_real64/sqrt(masses(iatom) * this%num_super)
279 i=i+1
280 end do
281 end do
282
283
284 call io_close(iunit)
285
286 else
287
288 this%num_modes = 0
289 this%num_super = 1
290
291 end if
292
293
294 pop_sub(phonon_modes_init)
295 end subroutine phonon_modes_init
296
297
299 subroutine phonon_modes_get_displacements(this, imode, Q, P, pos_disp_flat, vel_disp_flat)
300 class(phonon_modes_t), intent(in) :: this
301 integer, intent(in) :: imode
302 real(real64), intent(in) :: Q
303 real(real64), intent(in) :: P
304 real(real64), intent(out) :: pos_disp_flat(1:this%dim)
305 real(real64), intent(out) :: vel_disp_flat(1:this%dim)
306
307 pos_disp_flat = this%prefactor(:) * this%eigenvectors(:, imode) * q * this%l_m(imode)
308 vel_disp_flat = this%prefactor(:) * this%eigenvectors(:, imode) * p * this%l_m(imode) * this%frequencies(imode)
309
310 end subroutine phonon_modes_get_displacements
311
312
324 !
325 subroutine phonon_modes_sample(this, temperature, seed, pos_displacements, vel_displacements)
326 class(phonon_modes_t), intent(in) :: this
327 real(real64), intent(in) :: temperature
328 integer(int64), intent(in) :: seed
329 real(real64), intent(out) :: pos_displacements(:, :)
330 real(real64), intent(out) :: vel_displacements(:, :)
331
332 type(wigner_distribution_t) :: wigner
333 real(real64) :: sigma(1:this%num_modes), mu(1:this%num_modes)
334 real(real64) :: Q(1:this%num_modes), P(1:this%num_modes)
335 real(real64) :: pos_disp_flat(1:this%dim), vel_disp_flat(1:this%dim)
336 real(real64) :: beta_half
337 integer :: imode, iatom, dim_space, num_atoms
338
339 real(real64), parameter :: low_temperature_tolerance = 1.0e-6_real64
340
341 push_sub(phonon_modes_sample)
342
343 num_atoms = size(pos_displacements, 2)
344 dim_space = size(pos_displacements, 1)
345
346 call wigner%init(this%num_modes, seed)
347
348 ! The dimensionless mode variables are sampled with variance
349 ! sigma^2 = (1 + 2n)/2 = coth(beta*omega/2)/2, which reduces to 1/2 for T -> 0.
350 if (temperature < low_temperature_tolerance) then
351 sigma = 1.0_real64/sqrt(2.0_real64)
352 else
353 beta_half = m_one / (2 * p_kb * temperature)
354 sigma = 1.0_real64/sqrt(2.0_real64 * tanh(beta_half * this%frequencies(1:this%num_modes)))
355 end if
356 mu = m_zero
357
358
359 q = wigner%get(sigma, mu, wigner_q)
360 p = wigner%get(sigma, mu, wigner_p)
361
362 pos_displacements = m_zero
363 vel_displacements = m_zero
364
365 do imode = 1, this%num_modes
366 call phonon_modes_get_displacements(this, imode, q(imode), p(imode), pos_disp_flat, vel_disp_flat)
367
368 pos_displacements = pos_displacements + reshape(pos_disp_flat, [dim_space, num_atoms])
369 vel_displacements = vel_displacements + reshape(vel_disp_flat, [dim_space, num_atoms])
370
371 end do
372
373
374 call wigner%end()
375
376
377 write(message(1), '("Sampled displacements:")')
378 call messages_info(1, namespace=this%namespace)
379 do iatom = 1, num_atoms
380 write(message(1), '(2x,3E15.5)') pos_displacements(1:dim_space, iatom)
381 call messages_info(1, namespace=this%namespace)
382 end do
383
384 write(message(1), '("Sampled velocities:")')
385 call messages_info(1, namespace=this%namespace)
386 do iatom = 1, num_atoms
387 write(message(1), '(2x,3E15.5)') vel_displacements(1:dim_space, iatom)
388 call messages_info(1, namespace=this%namespace)
389 end do
390
391 pop_sub(phonon_modes_sample)
392 end subroutine phonon_modes_sample
393
397 !
398 subroutine next_data_line(iunit, line, filename, namespace, eof)
399 integer, intent(in) :: iunit
400 character(len=*), intent(out) :: line
401 character(len=*), intent(in) :: filename
402 type(namespace_t), intent(in) :: namespace
403 logical, optional, intent(out) :: eof
404
405 integer :: iostat
406
407 push_sub(next_data_line)
408
409 if (present(eof)) eof = .false.
410 do
411 read(iunit, '(A)', iostat=iostat) line
412 if (iostat /= 0) then
413 if (present(eof)) then
414 eof = .true.
415 line = ""
416 pop_sub(next_data_line)
417 return
418 end if
419 message(1) = "Unexpected end of phonon file ("//trim(filename)//")."
420 call messages_fatal(1, namespace=namespace)
421 end if
422 line = adjustl(line)
423 if (len_trim(line) == 0) cycle
424 if (line(1:1) == '#') cycle
425 exit
426 end do
427
428 pop_sub(next_data_line)
429 end subroutine next_data_line
430
431 subroutine phonon_modes_finalize(this)
432 type(phonon_modes_t), intent(inout) :: this
433
434 push_sub(phonon_modes_finalize)
435
436 safe_deallocate_a(this%alpha)
437
438 this%dim = 0
439 this%num_modes = 0
440
441 pop_sub(phonon_modes_finalize)
442 end subroutine phonon_modes_finalize
443
444end module phonon_modes_oct_m
double sqrt(double __x) __attribute__((__nothrow__
double tanh(double __x) __attribute__((__nothrow__
This module provides a general class for classical modes.
real(real64), parameter, public m_zero
Definition: global.F90:200
real(real64), parameter, public p_kb
Boltzmann constant in Ha/K.
Definition: global.F90:241
real(real64), parameter, public m_one
Definition: global.F90:201
Definition: io.F90:116
subroutine, public io_close(iunit, grp)
Definition: io.F90:467
integer function, public io_open(file, namespace, action, status, form, position, die, recl, grp)
Definition: io.F90:402
subroutine, public messages_warning(no_lines, all_nodes, namespace)
Definition: messages.F90:525
character(len=256), dimension(max_lines), public message
to be output by fatal, warning
Definition: messages.F90:162
subroutine, public messages_fatal(no_lines, only_root_writes, namespace)
Definition: messages.F90:410
subroutine, public messages_info(no_lines, iunit, debug_only, stress, all_nodes, namespace)
Definition: messages.F90:594
This module provides a class for (classical) phonon modes.
subroutine phonon_modes_finalize(this)
subroutine phonon_modes_get_displacements(this, imode, Q, P, pos_disp_flat, vel_disp_flat)
get the displacements for a single mode and the respective generalized coordinates.
subroutine phonon_modes_sample(this, temperature, seed, pos_displacements, vel_displacements)
Sample initial ionic displacements and velocities from the Wigner distribution of the modes.
subroutine next_data_line(iunit, line, filename, namespace, eof)
Read the next line that is neither blank nor a # comment.
subroutine phonon_modes_init(this, namespace, dim_space, num_atoms, periodic, masses)
Initialize the phonon modes.
This module defines the unit system, used for input and output.
type(unit_t), public unit_amu
Mass in atomic mass units (AKA Dalton).
integer, parameter, public wigner_q
integer, parameter, public wigner_p
This class describes classical modes, which are specified by their frequencies and eigenvectors.
This class describes phonon modes, which are specified by their frequencies and eigenvectors.
Class describing a Wigner distribution for sampling initial conditions in multi-trajectory Ehrenfest ...
int true(void)