Line data Source code
1 : !--------------------------------------------------------------------------------------------------!
2 : ! CP2K: A general program to perform molecular dynamics simulations !
3 : ! Copyright 2000-2024 CP2K developers group <https://cp2k.org> !
4 : ! !
5 : ! SPDX-License-Identifier: GPL-2.0-or-later !
6 : !--------------------------------------------------------------------------------------------------!
7 :
8 : ! **************************************************************************************************
9 : !> \brief Molecular symmetry routines
10 : !> \par History
11 : !> 2008 adapted from older routines by Matthias Krack
12 : !> \author jgh
13 : ! **************************************************************************************************
14 : MODULE molsym
15 :
16 : USE kinds, ONLY: dp
17 : USE mathconstants, ONLY: pi
18 : USE mathlib, ONLY: angle,&
19 : build_rotmat,&
20 : jacobi,&
21 : reflect_vector,&
22 : rotate_vector,&
23 : unit_matrix,&
24 : vector_product
25 : USE physcon, ONLY: angstrom
26 : USE util, ONLY: sort
27 : #include "./base/base_uses.f90"
28 :
29 : IMPLICIT NONE
30 : PRIVATE
31 :
32 : LOGICAL, PRIVATE, PARAMETER :: debug_this_module = .TRUE.
33 : CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'molsym'
34 :
35 : INTEGER, PARAMETER :: maxcn = 20, &
36 : maxsec = maxcn + 1, &
37 : maxses = 2*maxcn + 1, &
38 : maxsig = maxcn + 1, &
39 : maxsn = 2*maxcn
40 :
41 : PUBLIC :: molsym_type
42 : PUBLIC :: release_molsym, molecular_symmetry, print_symmetry
43 :
44 : ! **************************************************************************************************
45 : !> \brief Container for information about molecular symmetry
46 : !> \param ain : Atomic identification numbers (symmetry code).
47 : !> \param aw : Atomic weights for the symmetry analysis.
48 : !> \param eps_geo : Accuracy requested for analysis
49 : !> \param inptostd : Transformation matrix for input to standard orientation.
50 : !> \param point_group_symbol: Point group symbol.
51 : !> \param rotmat : Rotation matrix.
52 : !> \param sec : List of C axes
53 : !> (sec(:,i,j) => x,y,z of the ith j-fold C axis).
54 : !> \param ses : List of S axes
55 : !> (ses(:,i,j) => x,y,z of the ith j-fold S axis).
56 : !> \param sig : List of mirror planes
57 : !> (sig(:,i) => x,y,z of the ith mirror plane).
58 : !> \param center_of_mass : Shift vector for the center of mass.
59 : !> \param tenmat : Molecular tensor of inertia.
60 : !> \param tenval : Eigenvalues of the molecular tensor of inertia.
61 : !> \param tenvec : Eigenvectors of the molecular tensor of inertia.
62 : !> \param group_of : Group of equivalent atom.
63 : !> \param llequatom : Lower limit of a group in symequ_list.
64 : !> \param ncn : Degree of the C axis with highest degree.
65 : !> \param ndod : Number of substituted dodecahedral angles.
66 : !> \param nequatom : Number of equivalent atoms.
67 : !> \param ngroup : Number of groups of equivalent atoms.
68 : !> \param nico : Number of substituted icosahedral angles.
69 : !> \param nlin : Number of substituted angles of 180 degrees.
70 : !> \param nsec : Number of C axes.
71 : !> \param nses : Number of S axes.
72 : !> \param nsig : Number of mirror planes.
73 : !> \param nsn : Degree of the S axis with highest degree.
74 : !> \param ntet : Number of substituted tetrahedral angles.
75 : !> \param point_group_order : Group order.
76 : !> \param symequ_list : List of all atoms ordered in groups of equivalent atoms.
77 : !> \param ulequatom : Upper limit of a group in symequ_list.
78 : !> \param cubic : .TRUE., if a cubic point group was found (T,Th,Td,O,Oh,I,Ih).
79 : !> \param dgroup: .TRUE., if a point group of D symmetry was found (Dn,Dnh,Dnd)
80 : !> \param igroup: .TRUE., if a point group of icosahedral symmetry was found (I,Ih).
81 : !> \param invers: .TRUE., if the molecule has a center of inversion.
82 : !> \param linear: .TRUE., if the molecule is linear.
83 : !> \param maxis : .TRUE., if the molecule has a main axis.
84 : !> \param ogroup: .TRUE., if a point group of octahedral symmetry was found (O,Oh)
85 : !> \param sgroup: .TRUE., if a point group of S symmetry was found.
86 : !> \param sigmad: .TRUE., if there is a sigma_d mirror plane.
87 : !> \param sigmah: .TRUE., if there is a sigma_h mirror plane.
88 : !> \param sigmav: .TRUE., if there is a sigma_v mirror plane.
89 : !> \param tgroup: .TRUE., if a point group of tetrahedral symmetry was found (T,Th,Td).
90 : !> \par History
91 : !> 05.2008 created
92 : !> \author jgh
93 : ! **************************************************************************************************
94 : TYPE molsym_type
95 : CHARACTER(LEN=4) :: point_group_symbol
96 : INTEGER :: point_group_order
97 : INTEGER :: ncn, ndod, ngroup, nico, nlin, nsig, nsn, ntet
98 : LOGICAL :: cubic, dgroup, igroup, invers, linear, maxis, &
99 : ogroup, sgroup, sigmad, sigmah, sigmav, tgroup
100 : REAL(KIND=dp) :: eps_geo
101 : REAL(KIND=dp), DIMENSION(3) :: center_of_mass, tenval
102 : REAL(KIND=dp), DIMENSION(3) :: x_axis, y_axis, z_axis
103 : REAL(KIND=dp), DIMENSION(:), POINTER :: ain, aw
104 : REAL(KIND=dp), DIMENSION(3, 3) :: inptostd, rotmat, tenmat, tenvec
105 : REAL(KIND=dp), DIMENSION(3, maxsig) :: sig
106 : REAL(KIND=dp), DIMENSION(3, maxsec, 2:maxcn) :: sec
107 : REAL(KIND=dp), DIMENSION(3, maxses, 2:maxsn) :: ses
108 : INTEGER, DIMENSION(maxcn) :: nsec
109 : INTEGER, DIMENSION(maxsn) :: nses
110 : INTEGER, DIMENSION(:), POINTER :: group_of, llequatom, nequatom, &
111 : symequ_list, ulequatom
112 : END TYPE molsym_type
113 :
114 : ! **************************************************************************************************
115 :
116 : CONTAINS
117 :
118 : ! **************************************************************************************************
119 : !> \brief Create an object of molsym type
120 : !> \param sym ...
121 : !> \param natoms ...
122 : !> \author jgh
123 : ! **************************************************************************************************
124 58 : SUBROUTINE create_molsym(sym, natoms)
125 : TYPE(molsym_type), POINTER :: sym
126 : INTEGER, INTENT(IN) :: natoms
127 :
128 58 : IF (ASSOCIATED(sym)) CALL release_molsym(sym)
129 :
130 58 : ALLOCATE (sym)
131 :
132 : ALLOCATE (sym%ain(natoms), sym%aw(natoms), sym%group_of(natoms), sym%llequatom(natoms), &
133 870 : sym%nequatom(natoms), sym%symequ_list(natoms), sym%ulequatom(natoms))
134 :
135 58 : END SUBROUTINE create_molsym
136 :
137 : ! **************************************************************************************************
138 : !> \brief release an object of molsym type
139 : !> \param sym ...
140 : !> \author jgh
141 : ! **************************************************************************************************
142 58 : SUBROUTINE release_molsym(sym)
143 : TYPE(molsym_type), POINTER :: sym
144 :
145 58 : CPASSERT(ASSOCIATED(sym))
146 :
147 58 : IF (ASSOCIATED(sym%aw)) THEN
148 58 : DEALLOCATE (sym%aw)
149 : END IF
150 58 : IF (ASSOCIATED(sym%ain)) THEN
151 58 : DEALLOCATE (sym%ain)
152 : END IF
153 58 : IF (ASSOCIATED(sym%group_of)) THEN
154 58 : DEALLOCATE (sym%group_of)
155 : END IF
156 58 : IF (ASSOCIATED(sym%llequatom)) THEN
157 58 : DEALLOCATE (sym%llequatom)
158 : END IF
159 58 : IF (ASSOCIATED(sym%nequatom)) THEN
160 58 : DEALLOCATE (sym%nequatom)
161 : END IF
162 58 : IF (ASSOCIATED(sym%symequ_list)) THEN
163 58 : DEALLOCATE (sym%symequ_list)
164 : END IF
165 58 : IF (ASSOCIATED(sym%ulequatom)) THEN
166 58 : DEALLOCATE (sym%ulequatom)
167 : END IF
168 :
169 58 : DEALLOCATE (sym)
170 :
171 58 : END SUBROUTINE release_molsym
172 :
173 : ! **************************************************************************************************
174 :
175 : ! **************************************************************************************************
176 : !> \brief Add a new C_n axis to the list sec, but first check, if the
177 : !> Cn axis is already in the list.
178 : !> \param n ...
179 : !> \param a ...
180 : !> \param sym ...
181 : !> \par History
182 : !> Creation (19.10.98, Matthias Krack)
183 : !> \author jgh
184 : ! **************************************************************************************************
185 1419 : SUBROUTINE addsec(n, a, sym)
186 : INTEGER, INTENT(IN) :: n
187 : REAL(dp), DIMENSION(3), INTENT(IN) :: a
188 : TYPE(molsym_type), INTENT(inout) :: sym
189 :
190 : INTEGER :: isec
191 : REAL(dp) :: length_of_a, scapro
192 : REAL(dp), DIMENSION(3) :: d
193 :
194 1419 : length_of_a = SQRT(a(1)*a(1) + a(2)*a(2) + a(3)*a(3))
195 5676 : d(:) = a(:)/length_of_a
196 :
197 : ! Check, if the current Cn axis is already in the list
198 6384 : DO isec = 1, sym%nsec(n)
199 6088 : scapro = sym%sec(1, isec, n)*d(1) + sym%sec(2, isec, n)*d(2) + sym%sec(3, isec, n)*d(3)
200 6384 : IF (ABS(ABS(scapro) - 1.0_dp) < sym%eps_geo) RETURN
201 : END DO
202 296 : sym%ncn = MAX(sym%ncn, n)
203 :
204 : ! Add the new Cn axis to the list sec
205 296 : CPASSERT(sym%nsec(n) < maxsec)
206 296 : sym%nsec(1) = sym%nsec(1) + 1
207 296 : sym%nsec(n) = sym%nsec(n) + 1
208 1184 : sym%sec(:, sym%nsec(n), n) = d(:)
209 :
210 : END SUBROUTINE addsec
211 :
212 : ! **************************************************************************************************
213 : !> \brief Add a new Sn axis to the list ses, but first check, if the
214 : !> Sn axis is already in the list.
215 : !> \param n ...
216 : !> \param a ...
217 : !> \param sym ...
218 : !> \par History
219 : !> Creation (19.10.98, Matthias Krack)
220 : !> \author jgh
221 : ! **************************************************************************************************
222 284 : SUBROUTINE addses(n, a, sym)
223 : INTEGER, INTENT(IN) :: n
224 : REAL(dp), DIMENSION(3), INTENT(IN) :: a
225 : TYPE(molsym_type), INTENT(inout) :: sym
226 :
227 : INTEGER :: ises
228 : REAL(dp) :: length_of_a, scapro
229 : REAL(dp), DIMENSION(3) :: d
230 :
231 284 : length_of_a = SQRT(a(1)*a(1) + a(2)*a(2) + a(3)*a(3))
232 1136 : d(:) = a(:)/length_of_a
233 :
234 : ! Check, if the current Sn axis is already in the list
235 1051 : DO ises = 1, sym%nses(n)
236 888 : scapro = sym%ses(1, ises, n)*d(1) + sym%ses(2, ises, n)*d(2) + sym%ses(3, ises, n)*d(3)
237 1051 : IF (ABS(ABS(scapro) - 1.0_dp) < sym%eps_geo) RETURN
238 : END DO
239 163 : sym%nsn = MAX(sym%nsn, n)
240 :
241 : ! Add the new Sn axis to the list ses
242 163 : CPASSERT(sym%nses(n) < maxses)
243 163 : sym%nses(1) = sym%nses(1) + 1
244 163 : sym%nses(n) = sym%nses(n) + 1
245 652 : sym%ses(:, sym%nses(n), n) = d(:)
246 :
247 : END SUBROUTINE addses
248 :
249 : ! **************************************************************************************************
250 : !> \brief Add a new mirror plane to the list sig, but first check, if the
251 : !> normal vector of the mirror plane is already in the list.
252 : !> \param a ...
253 : !> \param sym ...
254 : !> \par History
255 : !> Creation (19.10.98, Matthias Krack)
256 : !> \author jgh
257 : ! **************************************************************************************************
258 597 : SUBROUTINE addsig(a, sym)
259 : REAL(dp), DIMENSION(3), INTENT(IN) :: a
260 : TYPE(molsym_type), INTENT(inout) :: sym
261 :
262 : INTEGER :: isig
263 : REAL(dp) :: length_of_a, scapro
264 : REAL(dp), DIMENSION(3) :: d
265 :
266 597 : length_of_a = SQRT(a(1)*a(1) + a(2)*a(2) + a(3)*a(3))
267 2388 : d(:) = a(:)/length_of_a
268 :
269 : ! Check, if the normal vector of the current mirror plane is already in the list
270 2426 : DO isig = 1, sym%nsig
271 2273 : scapro = sym%sig(1, isig)*d(1) + sym%sig(2, isig)*d(2) + sym%sig(3, isig)*d(3)
272 2426 : IF (ABS(ABS(scapro) - 1.0_dp) < sym%eps_geo) RETURN
273 : END DO
274 :
275 : ! Add the normal vector of the new mirror plane to the list sig
276 153 : CPASSERT(sym%nsig < maxsig)
277 153 : sym%nsig = sym%nsig + 1
278 612 : sym%sig(:, sym%nsig) = d(:)
279 :
280 : END SUBROUTINE addsig
281 :
282 : ! **************************************************************************************************
283 : !> \brief Symmetry handling for only one atom.
284 : !> \param sym ...
285 : !> \par History
286 : !> Creation (19.10.98, Matthias Krack)
287 : !> \author jgh
288 : ! **************************************************************************************************
289 1 : SUBROUTINE atomsym(sym)
290 : TYPE(molsym_type), INTENT(inout) :: sym
291 :
292 : ! Set point group symbol
293 :
294 1 : sym%point_group_symbol = "R(3)"
295 :
296 : ! Set variables like D*h
297 1 : sym%linear = .TRUE.
298 1 : sym%invers = .TRUE.
299 1 : sym%dgroup = .TRUE.
300 1 : sym%sigmah = .TRUE.
301 :
302 1 : END SUBROUTINE atomsym
303 :
304 : ! **************************************************************************************************
305 : !> \brief Search for point groups with AXial SYMmetry (Cn,Cnh,Cnv,Dn,Dnh,Dnd,S2n).
306 : !> \param coord ...
307 : !> \param sym ...
308 : !> \par History
309 : !> Creation (19.10.98, Matthias Krack)
310 : !> \author jgh
311 : ! **************************************************************************************************
312 45 : SUBROUTINE axsym(coord, sym)
313 : REAL(Kind=dp), DIMENSION(:, :), INTENT(inout) :: coord
314 : TYPE(molsym_type), INTENT(inout) :: sym
315 :
316 : INTEGER :: iatom, isig, jatom, m, n, natoms, nx, &
317 : ny, nz
318 : REAL(dp) :: phi
319 : REAL(dp), DIMENSION(3) :: a, b, d
320 :
321 : ! Find the correct main axis and rotate main axis to z axis
322 :
323 45 : IF ((sym%ncn == 2) .AND. (sym%nses(2) == 3)) THEN
324 2 : IF (sym%nsn == 4) THEN
325 : ! Special case: D2d
326 0 : phi = angle(sym%ses(:, 1, sym%nsn), sym%z_axis(:))
327 0 : d(:) = vector_product(sym%ses(:, 1, sym%nsn), sym%z_axis(:))
328 0 : CALL rotate_molecule(phi, d(:), sym, coord)
329 : ELSE
330 : ! Special cases: D2 and D2h
331 2 : phi = 0.5_dp*pi
332 2 : nx = naxis(sym%x_axis(:), coord, sym)
333 2 : ny = naxis(sym%y_axis(:), coord, sym)
334 2 : nz = naxis(sym%z_axis(:), coord, sym)
335 2 : IF ((nx > ny) .AND. (nx > nz)) THEN
336 0 : CALL rotate_molecule(-phi, sym%y_axis(:), sym, coord)
337 2 : ELSE IF ((ny > nz) .AND. (ny > nx)) THEN
338 0 : CALL rotate_molecule(phi, sym%x_axis(:), sym, coord)
339 : END IF
340 : END IF
341 : ELSE
342 43 : phi = angle(sym%sec(:, 1, sym%ncn), sym%z_axis(:))
343 43 : d(:) = vector_product(sym%sec(:, 1, sym%ncn), sym%z_axis(:))
344 43 : CALL rotate_molecule(phi, d(:), sym, coord)
345 : END IF
346 :
347 : ! Search for C2 axes perpendicular to the main axis
348 : ! Loop over all pairs of atoms (except on the z axis)
349 45 : natoms = SIZE(coord, 2)
350 459 : DO iatom = 1, natoms
351 1656 : a(:) = coord(:, iatom)
352 459 : IF ((ABS(a(1)) > sym%eps_geo) .OR. (ABS(a(2)) > sym%eps_geo)) THEN
353 388 : a(3) = 0.0_dp
354 388 : IF (caxis(2, a(:), sym, coord)) CALL addsec(2, a(:), sym)
355 388 : d(:) = vector_product(a(:), sym%z_axis(:))
356 388 : IF (sigma(d(:), sym, coord)) CALL addsig(d(:), sym)
357 2283 : DO jatom = iatom + 1, natoms
358 7580 : b(:) = coord(:, jatom)
359 2309 : IF ((ABS(b(1)) > sym%eps_geo) .OR. (ABS(b(2)) > sym%eps_geo)) THEN
360 1871 : b(3) = 0.0_dp
361 1871 : phi = 0.5_dp*angle(a(:), b(:))
362 1871 : d(:) = vector_product(a(:), b(:))
363 1871 : b(:) = rotate_vector(a(:), phi, d(:))
364 1871 : IF (caxis(2, b(:), sym, coord)) CALL addsec(2, b(:), sym)
365 1871 : d(:) = vector_product(b(:), sym%z_axis)
366 1895 : IF (sigma(d(:), sym, coord)) CALL addsig(d(:), sym)
367 : END IF
368 : END DO
369 : END IF
370 : END DO
371 :
372 : ! Check the xy plane for mirror plane
373 45 : IF (sigma(sym%z_axis(:), sym, coord)) THEN
374 14 : CALL addsig(sym%z_axis(:), sym)
375 14 : sym%sigmah = .TRUE.
376 : END IF
377 :
378 : ! Set logical variables for point group determination ***
379 45 : IF (sym%nsec(2) > 1) THEN
380 21 : sym%dgroup = .TRUE.
381 21 : IF ((.NOT. sym%sigmah) .AND. (sym%nsig > 0)) sym%sigmad = .TRUE.
382 : ELSE
383 24 : IF ((.NOT. sym%sigmah) .AND. (sym%nsig > 0)) sym%sigmav = .TRUE.
384 : ! Cnh groups with n>3 were wrong
385 : ! IF (sym%nsn > 3) sym%sgroup = .TRUE.
386 24 : IF ((.NOT. sym%sigmah) .AND. (sym%nsn > 3)) sym%sgroup = .TRUE.
387 : END IF
388 :
389 : ! Rotate to standard orientation
390 45 : n = 0
391 45 : m = 0
392 45 : IF ((sym%dgroup .AND. sym%sigmah) .OR. (sym%dgroup .AND. sym%sigmad) .OR. sym%sigmav) THEN
393 : ! Dnh, Dnd or Cnv
394 133 : DO isig = 1, sym%nsig
395 133 : IF (ABS(sym%sig(3, isig)) < sym%eps_geo) THEN
396 105 : n = nsigma(sym%sig(:, isig), sym, coord)
397 105 : IF (n > m) THEN
398 21 : m = n
399 84 : a(:) = sym%sig(:, isig)
400 : END IF
401 : END IF
402 : END DO
403 21 : IF (m > 0) THEN
404 : ! Check for special case: C2v with all atoms in a plane
405 21 : IF (sym%sigmav .AND. (m == natoms)) THEN
406 1 : phi = angle(a(:), sym%x_axis(:))
407 1 : d(:) = vector_product(a(:), sym%x_axis(:))
408 : ELSE
409 20 : phi = angle(a(:), sym%y_axis(:))
410 20 : d(:) = vector_product(a(:), sym%y_axis(:))
411 : END IF
412 21 : CALL rotate_molecule(phi, d(:), sym, coord)
413 : END IF
414 24 : ELSE IF (sym%sigmah) THEN
415 : ! Cnh
416 81 : DO iatom = 1, natoms
417 81 : IF (ABS(coord(3, iatom)) < sym%eps_geo) THEN
418 70 : n = naxis(coord(:, iatom), coord, sym)
419 70 : IF (n > m) THEN
420 28 : m = n
421 28 : a(:) = coord(:, iatom)
422 : END IF
423 : END IF
424 : END DO
425 7 : IF (m > 0) THEN
426 7 : phi = angle(a(:), sym%x_axis(:))
427 7 : d(:) = vector_product(a(:), sym%x_axis(:))
428 7 : CALL rotate_molecule(phi, d(:), sym, coord)
429 : END IF
430 : ! No action for Dn, Cn or S2n ***
431 : END IF
432 :
433 45 : END SUBROUTINE axsym
434 :
435 : ! **************************************************************************************************
436 : !> \brief Generate a symmetry list to identify equivalent atoms.
437 : !> \param sym ...
438 : !> \param coord ...
439 : !> \par History
440 : !> Creation (19.10.98, Matthias Krack)
441 : !> \author jgh
442 : ! **************************************************************************************************
443 58 : SUBROUTINE build_symequ_list(sym, coord)
444 : TYPE(molsym_type), INTENT(inout) :: sym
445 : REAL(Kind=dp), DIMENSION(:, :), INTENT(inout) :: coord
446 :
447 : INTEGER :: i, iatom, icn, iequatom, incr, isec, &
448 : ises, isig, isn, jatom, jcn, jsn, &
449 : natoms
450 : REAL(KIND=dp) :: phi
451 : REAL(KIND=dp), DIMENSION(3) :: a
452 :
453 58 : natoms = SIZE(coord, 2)
454 :
455 58 : IF (sym%sigmah) THEN
456 : incr = 1
457 : ELSE
458 41 : incr = 2
459 : END IF
460 :
461 : ! Initialize the atom and the group counter
462 58 : iatom = 1
463 58 : sym%ngroup = 1
464 :
465 : loop: DO
466 :
467 : ! Loop over all mirror planes
468 332 : DO isig = 1, sym%nsig
469 226 : a(:) = reflect_vector(coord(:, iatom), sym%sig(:, isig))
470 226 : iequatom = equatom(iatom, a(:), sym, coord)
471 332 : IF ((iequatom > 0) .AND. (.NOT. in_symequ_list(iequatom, sym))) THEN
472 111 : sym%ulequatom(sym%ngroup) = sym%ulequatom(sym%ngroup) + 1
473 111 : sym%symequ_list(sym%ulequatom(sym%ngroup)) = iequatom
474 111 : sym%nequatom(sym%ngroup) = sym%nequatom(sym%ngroup) + 1
475 : END IF
476 : END DO
477 :
478 : ! Loop over all Cn axes
479 445 : DO icn = 2, sym%ncn
480 829 : DO isec = 1, sym%nsec(icn)
481 1465 : DO jcn = 1, icn - 1
482 1126 : IF (newse(jcn, icn)) THEN
483 644 : phi = 2.0_dp*pi*REAL(jcn, KIND=dp)/REAL(icn, KIND=dp)
484 644 : a(:) = rotate_vector(coord(:, iatom), phi, sym%sec(:, isec, icn))
485 644 : iequatom = equatom(iatom, a(:), sym, coord)
486 644 : IF ((iequatom > 0) .AND. (.NOT. in_symequ_list(iequatom, sym))) THEN
487 328 : sym%ulequatom(sym%ngroup) = sym%ulequatom(sym%ngroup) + 1
488 328 : sym%symequ_list(sym%ulequatom(sym%ngroup)) = iequatom
489 328 : sym%nequatom(sym%ngroup) = sym%nequatom(sym%ngroup) + 1
490 : END IF
491 : END IF
492 : END DO
493 : END DO
494 : END DO
495 :
496 : ! Loop over all Sn axes
497 339 : DO isn = 2, sym%nsn
498 442 : DO ises = 1, sym%nses(isn)
499 643 : DO jsn = 1, isn - 1, incr
500 410 : IF (newse(jsn, isn)) THEN
501 243 : phi = 2.0_dp*pi*REAL(jsn, KIND=dp)/REAL(isn, KIND=dp)
502 243 : a(:) = rotate_vector(coord(:, iatom), phi, sym%ses(:, ises, isn))
503 972 : a(:) = reflect_vector(a(:), sym%ses(:, ises, isn))
504 243 : iequatom = equatom(iatom, a(:), sym, coord)
505 243 : IF ((iequatom > 0) .AND. (.NOT. in_symequ_list(iequatom, sym))) THEN
506 30 : sym%ulequatom(sym%ngroup) = sym%ulequatom(sym%ngroup) + 1
507 30 : sym%symequ_list(sym%ulequatom(sym%ngroup)) = iequatom
508 30 : sym%nequatom(sym%ngroup) = sym%nequatom(sym%ngroup) + 1
509 : END IF
510 : END IF
511 : END DO
512 : END DO
513 : END DO
514 :
515 : ! Exit loop, if all atoms are in the list
516 106 : IF (sym%ulequatom(sym%ngroup) == natoms) EXIT
517 :
518 : ! Search for the next atom which is not in the list yet
519 156 : DO jatom = 2, natoms
520 98 : IF (.NOT. in_symequ_list(jatom, sym)) THEN
521 48 : iatom = jatom
522 48 : sym%ngroup = sym%ngroup + 1
523 48 : sym%llequatom(sym%ngroup) = sym%ulequatom(sym%ngroup - 1) + 1
524 48 : sym%ulequatom(sym%ngroup) = sym%llequatom(sym%ngroup)
525 48 : sym%symequ_list(sym%llequatom(sym%ngroup)) = iatom
526 48 : CYCLE loop
527 : END IF
528 : END DO
529 :
530 : END DO loop
531 :
532 : ! Generate list vector group_of
533 164 : DO i = 1, sym%ngroup
534 739 : DO iequatom = sym%llequatom(i), sym%ulequatom(i)
535 681 : sym%group_of(sym%symequ_list(iequatom)) = i
536 : END DO
537 : END DO
538 :
539 58 : END SUBROUTINE build_symequ_list
540 :
541 : ! **************************************************************************************************
542 : !> \brief Rotate the molecule about a n-fold axis defined by the vector a
543 : !> using a rotation angle of 2*pi/n. Check, if the axis is a Cn axis.
544 : !> \param n ...
545 : !> \param a ...
546 : !> \param sym ...
547 : !> \param coord ...
548 : !> \return ...
549 : !> \par History
550 : !> Creation (19.10.98, Matthias Krack)
551 : !> \author jgh
552 : ! **************************************************************************************************
553 5543 : FUNCTION caxis(n, a, sym, coord)
554 : INTEGER, INTENT(IN) :: n
555 : REAL(KIND=dp), DIMENSION(3), INTENT(IN) :: a
556 : TYPE(molsym_type), INTENT(inout) :: sym
557 : REAL(Kind=dp), DIMENSION(:, :), INTENT(inout) :: coord
558 : LOGICAL :: caxis
559 :
560 : INTEGER :: iatom, natoms
561 : REAL(KIND=dp) :: length_of_a, phi
562 : REAL(KIND=dp), DIMENSION(3) :: b
563 :
564 5543 : caxis = .FALSE.
565 5543 : length_of_a = SQRT(a(1)*a(1) + a(2)*a(2) + a(3)*a(3))
566 :
567 : ! Check the length of the axis vector a
568 5543 : natoms = SIZE(coord, 2)
569 5543 : IF (length_of_a > sym%eps_geo) THEN
570 : ! Calculate the rotation angle phi and build up the rotation matrix rotmat
571 5409 : phi = 2.0_dp*pi/REAL(n, dp)
572 5409 : CALL build_rotmat(phi, a(:), sym%rotmat(:, :))
573 : ! Check all atoms
574 18830 : DO iatom = 1, natoms
575 : ! Rotate the actual atom by 2*pi/n about a
576 234507 : b(:) = MATMUL(sym%rotmat(:, :), coord(:, iatom))
577 : ! Check if the coordinates of the rotated atom
578 : ! are in the coordinate set of the molecule
579 18830 : IF (equatom(iatom, b(:), sym, coord) == 0) RETURN
580 : END DO
581 : ! The axis defined by vector a is a Cn axis, thus return with caxis = .TRUE.
582 : caxis = .TRUE.
583 : END IF
584 :
585 : END FUNCTION caxis
586 :
587 : ! **************************************************************************************************
588 : !> \brief Check for a point group of cubic symmetry (I,Ih,O,Oh,T,Th,Td).
589 : !> \param sym ...
590 : !> \param coord ...
591 : !> \param failed ...
592 : !> \par History
593 : !> Creation (19.10.98, Matthias Krack)
594 : !> \author jgh
595 : ! **************************************************************************************************
596 7 : SUBROUTINE cubsym(sym, coord, failed)
597 : TYPE(molsym_type), INTENT(inout) :: sym
598 : REAL(KIND=dp), DIMENSION(:, :), INTENT(inout) :: coord
599 : LOGICAL, INTENT(INOUT) :: failed
600 :
601 : INTEGER :: i, iatom, iax, ic3, isec, jatom, jc3, &
602 : jsec, katom, natoms, nc3
603 : REAL(KIND=dp) :: phi1, phi2, phidd, phidi, phiii
604 : REAL(KIND=dp), DIMENSION(3) :: a, b, c, d
605 :
606 : ! Angle between two adjacent atoms of the dodecahedron => <(C3,C3)
607 7 : phidd = ATAN(0.4_dp*SQRT(5.0_dp))
608 : ! Angle between two adjacent atoms of the icosahedron and the dodecahedron => <(C5,C3)
609 7 : phidi = ATAN(3.0_dp - SQRT(5.0_dp))
610 : ! Angle between two adjacent atoms of the icosahedron <(C5,C5)
611 7 : phiii = ATAN(2.0_dp)
612 :
613 : ! Find a pair of C3 axes
614 7 : natoms = SIZE(coord, 2)
615 146 : DO iatom = 1, natoms
616 : ! Check all atomic vectors for C3 axis
617 146 : IF (caxis(3, coord(:, iatom), sym, coord)) THEN
618 2 : CALL addsec(3, coord(:, iatom), sym)
619 2 : IF (sym%nsec(3) > 1) EXIT
620 : END IF
621 : END DO
622 :
623 : ! Check the center of mass vector of a triangle
624 : ! defined by three atomic vectors for C3 axis
625 7 : IF (sym%nsec(3) < 2) THEN
626 :
627 6 : loop: DO iatom = 1, natoms
628 24 : a(:) = coord(:, iatom)
629 24 : DO jatom = iatom + 1, natoms
630 734 : DO katom = jatom + 1, natoms
631 : IF ((ABS(dist(coord(:, iatom), coord(:, jatom)) &
632 716 : - dist(coord(:, iatom), coord(:, katom))) < sym%eps_geo) .AND. &
633 : (ABS(dist(coord(:, iatom), coord(:, jatom)) &
634 18 : - dist(coord(:, jatom), coord(:, katom))) < sym%eps_geo)) THEN
635 48 : b(:) = a(:) + coord(:, jatom) + coord(:, katom)
636 12 : IF (caxis(3, b(:), sym, coord)) THEN
637 12 : CALL addsec(3, b(:), sym)
638 12 : IF (sym%nsec(3) > 1) EXIT loop
639 : END IF
640 : END IF
641 : END DO
642 : END DO
643 : END DO loop
644 :
645 : END IF
646 :
647 : ! Return after unsuccessful search for a pair of C3 axes
648 7 : IF (sym%nsec(3) < 2) THEN
649 0 : failed = .TRUE.
650 0 : RETURN
651 : END IF
652 :
653 : ! Generate the remaining C3 axes
654 16 : nc3 = 0
655 : DO
656 16 : nc3 = sym%nsec(3)
657 82 : DO ic3 = 1, nc3
658 264 : a(:) = sym%sec(:, ic3, 3)
659 462 : DO jc3 = 1, nc3
660 446 : IF (ic3 /= jc3) THEN
661 1256 : d(:) = sym%sec(:, jc3, 3)
662 942 : DO i = 1, 2
663 628 : phi1 = 2.0_dp*REAL(i, KIND=dp)*pi/3.0_dp
664 628 : b(:) = rotate_vector(a(:), phi1, d(:))
665 942 : CALL addsec(3, b(:), sym)
666 : END DO
667 : END IF
668 : END DO
669 : END DO
670 :
671 16 : IF (sym%nsec(3) > nc3) THEN
672 : ! New C3 axes were found
673 : CYCLE
674 : ELSE
675 : ! In the case of I or Ih there have to be more C3 axes
676 7 : IF (sym%nsec(3) == 4) THEN
677 20 : a(:) = sym%sec(:, 1, 3)
678 20 : b(:) = sym%sec(:, 2, 3)
679 5 : phi1 = 0.5_dp*angle(a(:), b(:))
680 5 : IF (phi1 < 0.5_dp*pi) phi1 = phi1 - 0.5_dp*pi
681 5 : d(:) = vector_product(a(:), b(:))
682 5 : b(:) = rotate_vector(a(:), phi1, d(:))
683 20 : c(:) = sym%sec(:, 3, 3)
684 5 : phi1 = 0.5_dp*angle(a(:), c(:))
685 5 : IF (phi1 < 0.5_dp*pi) phi1 = phi1 - 0.5_dp*pi
686 5 : d(:) = vector_product(a(:), c(:))
687 5 : c(:) = rotate_vector(a(:), phi1, d(:))
688 5 : d(:) = vector_product(b(:), c(:))
689 5 : phi1 = 0.5_dp*phidd
690 5 : a(:) = rotate_vector(b(:), phi1, d(:))
691 5 : IF (caxis(3, a(:), sym, coord)) THEN
692 0 : CALL addsec(3, a(:), sym)
693 : ELSE
694 5 : phi2 = 0.5_dp*pi - phi1
695 5 : a(:) = rotate_vector(b(:), phi2, d(:))
696 5 : IF (caxis(3, a(:), sym, coord)) CALL addsec(3, a(:), sym)
697 : END IF
698 :
699 5 : IF (sym%nsec(3) > 4) CYCLE
700 :
701 2 : ELSE IF (sym%nsec(3) /= 10) THEN
702 :
703 : ! C3 axes found, but only 4 or 10 are possible
704 0 : failed = .TRUE.
705 0 : RETURN
706 :
707 : END IF
708 :
709 : ! Exit loop, if 4 or 10 C3 axes were found
710 9 : EXIT
711 :
712 : END IF
713 :
714 : END DO
715 :
716 : ! Loop over all pairs of C3 axes to find all C5, C4 and C2 axes
717 47 : DO isec = 1, sym%nsec(3)
718 :
719 160 : a(:) = sym%sec(:, isec, 3)
720 167 : DO jsec = isec + 1, sym%nsec(3)
721 120 : phi1 = 0.5_dp*angle(a(:), sym%sec(:, jsec, 3))
722 120 : d(:) = vector_product(a(:), sym%sec(:, jsec, 3))
723 :
724 : ! Check for C5 axis (I,Ih)
725 120 : IF (sym%nsec(3) == 10) THEN
726 90 : b(:) = rotate_vector(a(:), phidi, d(:))
727 90 : IF (caxis(5, b(:), sym, coord)) THEN
728 14 : CALL addsec(5, b(:), sym)
729 14 : phi1 = phidi + phiii
730 14 : b(:) = rotate_vector(a(:), phi1, d(:))
731 14 : IF (caxis(5, b(:), sym, coord)) CALL addsec(5, b(:), sym)
732 : END IF
733 : END IF
734 :
735 : ! Check for C4 (O,Oh), C2 and S2 (center of inversion) axis
736 360 : DO i = 0, 1
737 240 : phi2 = phi1 - 0.5_dp*REAL(i, KIND=dp)*pi
738 240 : b(:) = rotate_vector(a(:), phi2, d(:))
739 240 : IF (caxis(2, b(:), sym, coord)) THEN
740 134 : CALL addsec(2, b(:), sym)
741 134 : IF (sym%nsec(3) == 4) THEN
742 42 : IF (caxis(4, b(:), sym, coord)) CALL addsec(4, b(:), sym)
743 : END IF
744 134 : IF (saxis(2, b(:), sym, coord)) THEN
745 64 : CALL addses(2, b(:), sym)
746 64 : sym%invers = .TRUE.
747 : END IF
748 : END IF
749 360 : IF (sigma(b(:), sym, coord)) CALL addsig(b(:), sym)
750 : END DO
751 :
752 : ! Check for mirror plane
753 160 : IF (sigma(d(:), sym, coord)) CALL addsig(d(:), sym)
754 :
755 : END DO
756 :
757 : END DO
758 :
759 : ! Set the logical variables for point group determination
760 7 : iax = sym%ncn
761 :
762 7 : IF ((sym%ncn == 5) .AND. (sym%nsec(5) > 1)) THEN
763 2 : sym%igroup = .TRUE.
764 5 : ELSE IF ((sym%ncn == 4) .AND. (sym%nsec(4) > 1)) THEN
765 2 : sym%ogroup = .TRUE.
766 3 : ELSE IF ((sym%ncn == 3) .AND. (sym%nsec(3) > 1)) THEN
767 3 : sym%tgroup = .TRUE.
768 3 : IF ((.NOT. sym%invers) .AND. (sym%nsig > 0)) sym%sigmad = .TRUE.
769 : iax = 2
770 : ELSE
771 : ! No main axis found
772 0 : failed = .TRUE.
773 0 : RETURN
774 : END IF
775 :
776 : ! Rotate molecule to standard orientation
777 7 : phi1 = angle(sym%sec(:, 1, iax), sym%z_axis(:))
778 7 : d(:) = vector_product(sym%sec(:, 1, iax), sym%z_axis(:))
779 7 : CALL rotate_molecule(phi1, d(:), sym, coord)
780 :
781 28 : a(:) = sym%sec(:, 2, iax)
782 7 : a(3) = 0.0_dp
783 7 : phi2 = angle(a(:), sym%x_axis(:))
784 7 : d(:) = vector_product(a(:), sym%x_axis(:))
785 7 : CALL rotate_molecule(phi2, d(:), sym, coord)
786 :
787 : END SUBROUTINE cubsym
788 :
789 : ! **************************************************************************************************
790 : !> \brief The number of a equivalent atom to atoma is returned. If there
791 : !> is no equivalent atom, then zero is returned. The cartesian
792 : !> coordinates of the equivalent atom are defined by the vector a.
793 : !> \param atoma ...
794 : !> \param a ...
795 : !> \param sym ...
796 : !> \param coord ...
797 : !> \return ...
798 : !> \par History
799 : !> Creation (19.10.98, Matthias Krack)
800 : !> \author jgh
801 : ! **************************************************************************************************
802 38170 : FUNCTION equatom(atoma, a, sym, coord)
803 : INTEGER, INTENT(IN) :: atoma
804 : REAL(KIND=dp), DIMENSION(3), INTENT(IN) :: a
805 : TYPE(molsym_type), INTENT(inout) :: sym
806 : REAL(KIND=dp), DIMENSION(:, :), INTENT(inout) :: coord
807 : INTEGER :: equatom
808 :
809 : INTEGER :: iatom, natoms
810 :
811 38170 : equatom = 0
812 38170 : natoms = SIZE(coord, 2)
813 413408 : DO iatom = 1, natoms
814 : IF ((ABS(sym%ain(iatom) - sym%ain(atoma)) < TINY(0.0_dp)) .AND. &
815 : (ABS(a(1) - coord(1, iatom)) < sym%eps_geo) .AND. &
816 400638 : (ABS(a(2) - coord(2, iatom)) < sym%eps_geo) .AND. &
817 12770 : (ABS(a(3) - coord(3, iatom)) < sym%eps_geo)) THEN
818 38170 : equatom = iatom
819 : RETURN
820 : END IF
821 : END DO
822 :
823 : END FUNCTION equatom
824 :
825 : ! **************************************************************************************************
826 : !> \brief Calculate the order of the point group.
827 : !> \param sym ...
828 : !> \par History
829 : !> Creation (19.10.98, Matthias Krack)
830 : !> \author jgh
831 : ! **************************************************************************************************
832 55 : SUBROUTINE get_point_group_order(sym)
833 : TYPE(molsym_type), INTENT(inout) :: sym
834 :
835 : INTEGER :: icn, incr, isec, ises, isn, jcn, jsn
836 :
837 : ! Count all symmetry elements of the symmetry group
838 : ! First E and all mirror planes
839 55 : sym%point_group_order = 1 + sym%nsig
840 :
841 : ! Loop over all C axes
842 249 : DO icn = 2, sym%ncn
843 545 : DO isec = 1, sym%nsec(icn)
844 1021 : DO jcn = 1, icn - 1
845 827 : IF (newse(jcn, icn)) sym%point_group_order = sym%point_group_order + 1
846 : END DO
847 : END DO
848 : END DO
849 :
850 : ! Loop over all S axes
851 55 : IF (sym%sigmah) THEN
852 : incr = 1
853 : ELSE
854 40 : incr = 2
855 : END IF
856 :
857 212 : DO isn = 2, sym%nsn
858 285 : DO ises = 1, sym%nses(isn)
859 452 : DO jsn = 1, isn - 1, incr
860 295 : IF (newse(jsn, isn)) sym%point_group_order = sym%point_group_order + 1
861 : END DO
862 : END DO
863 : END DO
864 :
865 55 : END SUBROUTINE get_point_group_order
866 :
867 : ! **************************************************************************************************
868 : !> \brief Get the point group symbol.
869 : !> \param sym ...
870 : !> \par History
871 : !> Creation (19.10.98, Matthias Krack)
872 : !> \author jgh
873 : ! **************************************************************************************************
874 57 : SUBROUTINE get_point_group_symbol(sym)
875 : TYPE(molsym_type), INTENT(inout) :: sym
876 :
877 : CHARACTER(LEN=4) :: degree
878 :
879 57 : IF (sym%linear) THEN
880 2 : IF (sym%invers) THEN
881 1 : sym%point_group_symbol = "D*h"
882 : ELSE
883 1 : sym%point_group_symbol = "C*v"
884 : END IF
885 55 : ELSE IF (sym%cubic) THEN
886 7 : IF (sym%igroup) THEN
887 2 : sym%point_group_symbol = "I"
888 5 : ELSE IF (sym%ogroup) THEN
889 2 : sym%point_group_symbol = "O"
890 3 : ELSE IF (sym%tgroup) THEN
891 3 : sym%point_group_symbol = "T"
892 : END IF
893 7 : IF (sym%invers) THEN
894 3 : sym%point_group_symbol = TRIM(sym%point_group_symbol)//"h"
895 4 : ELSE IF (sym%sigmad) THEN
896 1 : sym%point_group_symbol = TRIM(sym%point_group_symbol)//"d"
897 : END IF
898 48 : ELSE IF (sym%maxis) THEN
899 45 : IF (sym%dgroup) THEN
900 21 : WRITE (degree, "(I3)") sym%ncn
901 21 : sym%point_group_symbol = "D"//TRIM(ADJUSTL(degree))
902 21 : IF (sym%sigmah) THEN
903 7 : sym%point_group_symbol = TRIM(sym%point_group_symbol)//"h"
904 14 : ELSE IF (sym%sigmad) THEN
905 7 : sym%point_group_symbol = TRIM(sym%point_group_symbol)//"d"
906 : END IF
907 24 : ELSE IF (sym%sgroup) THEN
908 3 : WRITE (degree, "(I3)") sym%nsn
909 3 : sym%point_group_symbol = "S"//TRIM(ADJUSTL(degree))
910 : ELSE
911 21 : WRITE (degree, "(I3)") sym%ncn
912 21 : sym%point_group_symbol = "C"//TRIM(ADJUSTL(degree))
913 21 : IF (sym%sigmah) THEN
914 7 : sym%point_group_symbol = TRIM(sym%point_group_symbol)//"h"
915 14 : ELSE IF (sym%sigmav) THEN
916 7 : sym%point_group_symbol = TRIM(sym%point_group_symbol)//"v"
917 : END IF
918 : END IF
919 : ELSE
920 3 : IF (sym%invers) THEN
921 1 : sym%point_group_symbol = "Ci"
922 2 : ELSE IF (sym%sigmah) THEN
923 1 : sym%point_group_symbol = "Cs"
924 : ELSE
925 1 : sym%point_group_symbol = "C1"
926 : END IF
927 : END IF
928 :
929 57 : END SUBROUTINE get_point_group_symbol
930 :
931 : ! **************************************************************************************************
932 : !> \brief Initialization of the global variables of module symmetry.
933 : !> \param sym ...
934 : !> \param atype ...
935 : !> \param weight ...
936 : !> \par History
937 : !> Creation (19.10.98, Matthias Krack)
938 : !> \author jgh
939 : ! **************************************************************************************************
940 58 : SUBROUTINE init_symmetry(sym, atype, weight)
941 : TYPE(molsym_type), INTENT(inout) :: sym
942 : INTEGER, DIMENSION(:), INTENT(IN) :: atype
943 : REAL(KIND=dp), DIMENSION(:), INTENT(IN) :: weight
944 :
945 : INTEGER :: i, iatom, natoms
946 :
947 : ! Define the Cartesian axis vectors
948 232 : sym%x_axis = (/1.0_dp, 0.0_dp, 0.0_dp/)
949 232 : sym%y_axis = (/0.0_dp, 1.0_dp, 0.0_dp/)
950 232 : sym%z_axis = (/0.0_dp, 0.0_dp, 1.0_dp/)
951 :
952 : ! Initialize lists for symmetry elements
953 93728 : sym%sec(:, :, :) = 0.0_dp
954 373288 : sym%ses(:, :, :) = 0.0_dp
955 4930 : sym%sig(:, :) = 0.0_dp
956 :
957 232 : sym%center_of_mass(:) = 0.0_dp
958 :
959 : ! Initialize symmetry element counters
960 58 : sym%ncn = 1
961 58 : sym%nsn = 1
962 1218 : sym%nsec(:) = 0
963 2378 : sym%nses(:) = 0
964 58 : sym%nsig = 0
965 :
966 : ! Initialize logical variables
967 58 : sym%cubic = .FALSE.
968 58 : sym%dgroup = .FALSE.
969 58 : sym%igroup = .FALSE.
970 58 : sym%invers = .FALSE.
971 58 : sym%linear = .FALSE.
972 58 : sym%maxis = .FALSE.
973 58 : sym%ogroup = .FALSE.
974 58 : sym%sgroup = .FALSE.
975 58 : sym%sigmad = .FALSE.
976 58 : sym%sigmah = .FALSE.
977 58 : sym%sigmav = .FALSE.
978 58 : sym%tgroup = .FALSE.
979 :
980 : ! Initialize list of equivalent atoms (C1 symmetry)
981 58 : natoms = SIZE(sym%nequatom)
982 58 : sym%ngroup = natoms
983 633 : sym%nequatom(:) = 1
984 633 : DO i = 1, sym%ngroup
985 575 : sym%group_of(i) = i
986 575 : sym%llequatom(i) = i
987 575 : sym%symequ_list(i) = i
988 633 : sym%ulequatom(i) = i
989 : END DO
990 :
991 58 : sym%point_group_order = -1
992 :
993 633 : DO iatom = 1, natoms
994 633 : sym%aw(iatom) = weight(iatom)
995 : END DO
996 :
997 : ! Generate atomic identification numbers (symmetry code) ***
998 633 : sym%ain(:) = REAL(atype(:), KIND=dp) + sym%aw(:)
999 :
1000 : ! Initialize the transformation matrix for input orientation -> standard orientation
1001 58 : CALL unit_matrix(sym%inptostd(:, :))
1002 :
1003 58 : END SUBROUTINE init_symmetry
1004 :
1005 : ! **************************************************************************************************
1006 : !> \brief Return .TRUE., if the atom atoma is in the list symequ_list.
1007 : !> \param atoma ...
1008 : !> \param sym ...
1009 : !> \return ...
1010 : !> \par History
1011 : !> Creation (21.04.95, Matthias Krack)
1012 : !> \author jgh
1013 : ! **************************************************************************************************
1014 1211 : FUNCTION in_symequ_list(atoma, sym)
1015 : INTEGER, INTENT(IN) :: atoma
1016 : TYPE(molsym_type), INTENT(inout) :: sym
1017 : LOGICAL :: in_symequ_list
1018 :
1019 : INTEGER :: iatom
1020 :
1021 1211 : in_symequ_list = .FALSE.
1022 :
1023 7969 : DO iatom = 1, sym%ulequatom(sym%ngroup)
1024 7969 : IF (sym%symequ_list(iatom) == atoma) THEN
1025 1211 : in_symequ_list = .TRUE.
1026 : RETURN
1027 : END IF
1028 : END DO
1029 :
1030 : END FUNCTION in_symequ_list
1031 :
1032 : ! **************************************************************************************************
1033 : !> \brief Search for point groups of LOW SYMmetry (Ci,Cs).
1034 : !> \param sym ...
1035 : !> \param coord ...
1036 : !> \par History
1037 : !> Creation (21.04.95, Matthias Krack)
1038 : !> \author jgh
1039 : ! **************************************************************************************************
1040 3 : SUBROUTINE lowsym(sym, coord)
1041 : TYPE(molsym_type), INTENT(inout) :: sym
1042 : REAL(KIND=dp), DIMENSION(:, :), INTENT(inout) :: coord
1043 :
1044 : REAL(KIND=dp) :: phi
1045 : REAL(KIND=dp), DIMENSION(3) :: d
1046 :
1047 3 : IF (sym%nsn == 2) THEN
1048 : ! Ci
1049 1 : sym%invers = .TRUE.
1050 1 : phi = angle(sym%ses(:, 1, 2), sym%z_axis(:))
1051 1 : d(:) = vector_product(sym%ses(:, 1, 2), sym%z_axis(:))
1052 1 : CALL rotate_molecule(phi, d(:), sym, coord)
1053 2 : ELSE IF (sym%nsig == 1) THEN
1054 : ! Cs
1055 1 : sym%sigmah = .TRUE.
1056 1 : phi = angle(sym%sig(:, 1), sym%z_axis(:))
1057 1 : d(:) = vector_product(sym%sig(:, 1), sym%z_axis(:))
1058 1 : CALL rotate_molecule(phi, d(:), sym, coord)
1059 : END IF
1060 :
1061 3 : END SUBROUTINE lowsym
1062 :
1063 : ! **************************************************************************************************
1064 : !> \brief Main program for symmetry analysis.
1065 : !> \param sym ...
1066 : !> \param eps_geo ...
1067 : !> \param coord ...
1068 : !> \param atype ...
1069 : !> \param weight ...
1070 : !> \par History
1071 : !> Creation (14.11.98, Matthias Krack)
1072 : !> \author jgh
1073 : ! **************************************************************************************************
1074 58 : SUBROUTINE molecular_symmetry(sym, eps_geo, coord, atype, weight)
1075 : TYPE(molsym_type), POINTER :: sym
1076 : REAL(KIND=dp), INTENT(IN) :: eps_geo
1077 : REAL(KIND=dp), DIMENSION(:, :), INTENT(inout) :: coord
1078 : INTEGER, DIMENSION(:), INTENT(IN) :: atype
1079 : REAL(KIND=dp), DIMENSION(:), INTENT(IN) :: weight
1080 :
1081 : CHARACTER(LEN=*), PARAMETER :: routineN = 'molecular_symmetry'
1082 :
1083 : INTEGER :: handle, natoms
1084 :
1085 58 : CALL timeset(routineN, handle)
1086 :
1087 : ! Perform memory allocation for the symmetry analysis
1088 58 : natoms = SIZE(coord, 2)
1089 58 : CALL create_molsym(sym, natoms)
1090 58 : sym%eps_geo = eps_geo
1091 :
1092 : ! Initialization of symmetry analysis
1093 58 : CALL init_symmetry(sym, atype, weight)
1094 :
1095 58 : IF (natoms == 1) THEN
1096 : ! Special case: only one atom
1097 1 : CALL atomsym(sym)
1098 : ELSE
1099 : ! Find molecular symmetry
1100 57 : CALL moleculesym(sym, coord)
1101 : ! Get point group and load point group table
1102 57 : CALL get_point_group_symbol(sym)
1103 : END IF
1104 :
1105 : ! Calculate group order
1106 58 : IF (.NOT. sym%linear) CALL get_point_group_order(sym)
1107 :
1108 : ! Generate a list of equivalent atoms
1109 58 : CALL build_symequ_list(sym, coord)
1110 :
1111 58 : CALL timestop(handle)
1112 :
1113 58 : END SUBROUTINE molecular_symmetry
1114 :
1115 : ! **************************************************************************************************
1116 : !> \brief Find the molecular symmetry.
1117 : !> \param sym ...
1118 : !> \param coord ...
1119 : !> \par History
1120 : !> Creation (14.11.98, Matthias Krack)
1121 : !> \author jgh
1122 : ! **************************************************************************************************
1123 57 : SUBROUTINE moleculesym(sym, coord)
1124 : TYPE(molsym_type), INTENT(inout) :: sym
1125 : REAL(KIND=dp), DIMENSION(:, :), INTENT(inout) :: coord
1126 :
1127 : INTEGER :: icn, isec, isn
1128 : LOGICAL :: failed
1129 : REAL(KIND=dp) :: eps_tenval
1130 :
1131 : ! Tolerance of degenerate eigenvalues for the molecular tensor of inertia
1132 :
1133 57 : eps_tenval = 0.01_dp*sym%eps_geo
1134 :
1135 : ! Calculate the molecular tensor of inertia
1136 57 : CALL tensor(sym, coord)
1137 : ! Use symmetry information from the eigenvalues of the molecular tensor of inertia
1138 57 : IF ((sym%tenval(3) - sym%tenval(1)) < eps_tenval) THEN
1139 : ! 0 < tenval(1) = tenval(2) = tenval(3)
1140 7 : sym%cubic = .TRUE.
1141 50 : ELSE IF ((sym%tenval(3) - sym%tenval(2)) < eps_tenval) THEN
1142 : ! 0 < tenval(1) < tenval(2) = tenval(3)
1143 : ! special case: 0 = tenval(1) < tenval(2) = tenval(3)
1144 14 : IF (sym%tenval(1) < eps_tenval) sym%linear = .TRUE.
1145 14 : CALL tracar(sym, coord, (/3, 1, 2/))
1146 : ELSE
1147 : ! 0 < tenval(1) = tenval(2) < tenval(3) or 0 < tenval(1) < tenval(2) < tenval(3)
1148 36 : CALL tracar(sym, coord, (/1, 2, 3/))
1149 : END IF
1150 :
1151 : ! Continue with the new coordinate axes
1152 : DO
1153 57 : failed = .FALSE.
1154 57 : IF (sym%cubic) THEN
1155 7 : CALL cubsym(sym, coord, failed)
1156 7 : IF (failed) THEN
1157 0 : sym%cubic = .FALSE.
1158 0 : CYCLE
1159 : END IF
1160 :
1161 50 : ELSE IF (sym%linear) THEN
1162 :
1163 : ! Linear molecule
1164 2 : IF (saxis(2, sym%z_axis(:), sym, coord)) THEN
1165 1 : sym%invers = .TRUE.
1166 1 : sym%dgroup = .TRUE.
1167 1 : sym%sigmah = .TRUE.
1168 : END IF
1169 :
1170 : ELSE
1171 :
1172 : ! Check the new coordinate axes for Cn axes
1173 960 : DO icn = 2, maxcn
1174 912 : IF (caxis(icn, sym%z_axis(:), sym, coord)) CALL addsec(icn, sym%z_axis(:), sym)
1175 912 : IF (caxis(icn, sym%x_axis(:), sym, coord)) CALL addsec(icn, sym%x_axis(:), sym)
1176 960 : IF (caxis(icn, sym%y_axis(:), sym, coord)) CALL addsec(icn, sym%y_axis(:), sym)
1177 : END DO
1178 :
1179 : ! Check the new coordinate axes for Sn axes
1180 1920 : DO isn = 2, maxsn
1181 1872 : IF (saxis(isn, sym%z_axis(:), sym, coord)) CALL addses(isn, sym%z_axis(:), sym)
1182 1872 : IF (saxis(isn, sym%x_axis(:), sym, coord)) CALL addses(isn, sym%x_axis(:), sym)
1183 1920 : IF (saxis(isn, sym%y_axis(:), sym, coord)) CALL addses(isn, sym%y_axis(:), sym)
1184 : END DO
1185 :
1186 : ! Check the new coordinate planes for mirror planes
1187 48 : IF (sigma(sym%z_axis(:), sym, coord)) CALL addsig(sym%z_axis(:), sym)
1188 48 : IF (sigma(sym%x_axis(:), sym, coord)) CALL addsig(sym%x_axis(:), sym)
1189 48 : IF (sigma(sym%y_axis(:), sym, coord)) CALL addsig(sym%y_axis(:), sym)
1190 :
1191 : ! There is a main axis (MAXIS = .TRUE.)
1192 48 : IF ((sym%ncn > 1) .OR. (sym%nsn > 3)) THEN
1193 45 : sym%maxis = .TRUE.
1194 45 : CALL axsym(coord, sym)
1195 : ELSE
1196 : ! Only low or no symmetry (Ci, Cs or C1)
1197 3 : CALL lowsym(sym, coord)
1198 : END IF
1199 :
1200 : END IF
1201 :
1202 57 : IF (.NOT. failed) EXIT
1203 :
1204 : END DO
1205 :
1206 : ! Find the remaining S axes
1207 57 : IF (.NOT. sym%linear) THEN
1208 249 : DO icn = 2, sym%ncn
1209 545 : DO isec = 1, sym%nsec(icn)
1210 296 : IF (saxis(icn, sym%sec(:, isec, icn), sym, coord)) &
1211 89 : CALL addses(icn, sym%sec(:, isec, icn), sym)
1212 296 : IF (saxis(2*icn, sym%sec(:, isec, icn), sym, coord)) &
1213 243 : CALL addses(2*icn, sym%sec(:, isec, icn), sym)
1214 : END DO
1215 : END DO
1216 : END IF
1217 :
1218 : ! Remove redundant S2 axes
1219 57 : IF (sym%nses(2) > 0) THEN
1220 16 : sym%nses(1) = sym%nses(1) - sym%nses(2)
1221 16 : sym%nses(2) = 0
1222 16 : CALL addses(2, sym%z_axis(:), sym)
1223 : END IF
1224 :
1225 57 : END SUBROUTINE moleculesym
1226 :
1227 : ! **************************************************************************************************
1228 : !> \brief The number of atoms which are placed on an axis defined by the vector a is returned.
1229 : !> \param a ...
1230 : !> \param coord ...
1231 : !> \param sym ...
1232 : !> \return ...
1233 : !> \par History
1234 : !> Creation (16.11.98, Matthias Krack)
1235 : !> \author jgh
1236 : ! **************************************************************************************************
1237 76 : FUNCTION naxis(a, coord, sym)
1238 : REAL(KIND=dp), DIMENSION(3), INTENT(IN) :: a
1239 : REAL(KIND=dp), DIMENSION(:, :), INTENT(inout) :: coord
1240 : TYPE(molsym_type), INTENT(inout) :: sym
1241 : INTEGER :: naxis
1242 :
1243 : INTEGER :: iatom, natoms
1244 : REAL(KIND=dp) :: length_of_a, length_of_b, scapro
1245 : REAL(KIND=dp), DIMENSION(3) :: a_norm, b, b_norm
1246 :
1247 76 : naxis = 0
1248 76 : natoms = SIZE(coord, 2)
1249 :
1250 76 : length_of_a = SQRT(a(1)*a(1) + a(2)*a(2) + a(3)*a(3))
1251 :
1252 : ! Check the length of vector a
1253 76 : IF (length_of_a > sym%eps_geo) THEN
1254 :
1255 956 : DO iatom = 1, natoms
1256 3520 : b(:) = coord(:, iatom)
1257 880 : length_of_b = SQRT(b(1)*b(1) + b(2)*b(2) + b(3)*b(3))
1258 : ! An atom in the origin counts for each axis
1259 956 : IF (length_of_b < sym%eps_geo) THEN
1260 0 : naxis = naxis + 1
1261 : ELSE
1262 3520 : a_norm = a(:)/length_of_a
1263 3520 : b_norm = b(:)/length_of_b
1264 880 : scapro = a_norm(1)*b_norm(1) + a_norm(2)*b_norm(2) + a_norm(3)*b_norm(3)
1265 880 : IF (ABS(ABS(scapro) - 1.0_dp) < sym%eps_geo) naxis = naxis + 1
1266 : END IF
1267 : END DO
1268 :
1269 : END IF
1270 :
1271 76 : END FUNCTION naxis
1272 :
1273 : ! **************************************************************************************************
1274 : !> \brief Return .FALSE., if the symmetry elements se1 and se2 are a pair of
1275 : !> redundant symmetry elements.
1276 : !> \param se1 ...
1277 : !> \param se2 ...
1278 : !> \return ...
1279 : !> \par History
1280 : !> Creation (16.11.98, Matthias Krack)
1281 : !> \author jgh
1282 : ! **************************************************************************************************
1283 1802 : FUNCTION newse(se1, se2)
1284 : INTEGER, INTENT(IN) :: se1, se2
1285 : LOGICAL :: newse
1286 :
1287 : INTEGER :: ise
1288 :
1289 1802 : newse = .TRUE.
1290 :
1291 3878 : DO ise = 2, MIN(se1, se2)
1292 3878 : IF ((MODULO(se1, ise) == 0) .AND. (MODULO(se2, ise) == 0)) THEN
1293 1802 : newse = .FALSE.
1294 : RETURN
1295 : END IF
1296 : END DO
1297 :
1298 : END FUNCTION newse
1299 :
1300 : ! **************************************************************************************************
1301 : !> \brief The number of atoms which are placed in a plane defined by the
1302 : !> the normal vector a is returned.
1303 : !> \param a ...
1304 : !> \param sym ...
1305 : !> \param coord ...
1306 : !> \return ...
1307 : !> \par History
1308 : !> Creation (16.11.98, Matthias Krack)
1309 : !> \author jgh
1310 : ! **************************************************************************************************
1311 105 : FUNCTION nsigma(a, sym, coord)
1312 : REAL(KIND=dp), DIMENSION(3), INTENT(IN) :: a
1313 : TYPE(molsym_type), INTENT(inout) :: sym
1314 : REAL(KIND=dp), DIMENSION(:, :), INTENT(inout) :: coord
1315 : INTEGER :: nsigma
1316 :
1317 : INTEGER :: iatom, natoms
1318 : REAL(KIND=dp) :: length_of_a, length_of_b, scapro
1319 : REAL(KIND=dp), DIMENSION(3) :: a_norm, b, b_norm
1320 :
1321 105 : nsigma = 0
1322 :
1323 105 : length_of_a = SQRT(a(1)*a(1) + a(2)*a(2) + a(3)*a(3))
1324 :
1325 : ! Check the length of vector a
1326 105 : IF (length_of_a > sym%eps_geo) THEN
1327 105 : natoms = SIZE(coord, 2)
1328 998 : DO iatom = 1, natoms
1329 3572 : b(:) = coord(:, iatom)
1330 893 : length_of_b = SQRT(b(1)*b(1) + b(2)*b(2) + b(3)*b(3))
1331 : ! An atom in the origin counts for each mirror plane
1332 998 : IF (length_of_b < sym%eps_geo) THEN
1333 0 : nsigma = nsigma + 1
1334 : ELSE
1335 3572 : a_norm = a(:)/length_of_a
1336 3572 : b_norm = b(:)/length_of_b
1337 893 : scapro = a_norm(1)*b_norm(1) + a_norm(2)*b_norm(2) + a_norm(3)*b_norm(3)
1338 893 : IF (ABS(scapro) < sym%eps_geo) nsigma = nsigma + 1
1339 : END IF
1340 : END DO
1341 : END IF
1342 :
1343 105 : END FUNCTION nsigma
1344 :
1345 : ! **************************************************************************************************
1346 : !> \brief Style the output of the symmetry elements.
1347 : !> \param se ...
1348 : !> \param eps ...
1349 : !> \par History
1350 : !> Creation (16.11.98, Matthias Krack)
1351 : !> \author jgh
1352 : ! **************************************************************************************************
1353 522 : SUBROUTINE outse(se, eps)
1354 : REAL(KIND=dp), DIMENSION(3), INTENT(INOUT) :: se
1355 : REAL(KIND=dp), INTENT(IN) :: eps
1356 :
1357 : ! Positive z component for all vectors
1358 :
1359 522 : IF (ABS(se(3)) < eps) THEN
1360 255 : IF (ABS(se(1)) < eps) THEN
1361 47 : se(2) = -se(2)
1362 : ELSE
1363 448 : IF (se(1) < 0.0_dp) se(1:2) = -se(1:2)
1364 : END IF
1365 : ELSE
1366 471 : IF (se(3) < 0.0_dp) se(:) = -se(:)
1367 : END IF
1368 :
1369 522 : END SUBROUTINE outse
1370 :
1371 : ! **************************************************************************************************
1372 : !> \brief Print the output of the symmetry analysis.
1373 : !> \param sym ...
1374 : !> \param coord ...
1375 : !> \param atype ...
1376 : !> \param element ...
1377 : !> \param z ...
1378 : !> \param weight ...
1379 : !> \param iw ...
1380 : !> \param plevel ...
1381 : !> \par History
1382 : !> Creation (16.11.98, Matthias Krack)
1383 : !> \author jgh
1384 : ! **************************************************************************************************
1385 58 : SUBROUTINE print_symmetry(sym, coord, atype, element, z, weight, iw, plevel)
1386 : TYPE(molsym_type), INTENT(inout) :: sym
1387 : REAL(KIND=dp), DIMENSION(:, :), INTENT(in) :: coord
1388 : INTEGER, DIMENSION(:), INTENT(in) :: atype
1389 : CHARACTER(LEN=*), DIMENSION(:), INTENT(in) :: element
1390 : INTEGER, DIMENSION(:), INTENT(in) :: z
1391 : REAL(KIND=dp), DIMENSION(:), INTENT(in) :: weight
1392 : INTEGER, INTENT(IN) :: iw, plevel
1393 :
1394 : REAL(KIND=dp), PARAMETER :: formatmaxval = 1.0E5_dp
1395 :
1396 : CHARACTER(LEN=3) :: string
1397 : INTEGER :: i, icn, iequatom, isec, ises, isig, isn, &
1398 : j, nequatom, secount
1399 58 : INTEGER, ALLOCATABLE, DIMENSION(:) :: equatomlist, idxlist
1400 :
1401 : ! Print point group symbol and point group order
1402 : WRITE (iw, "(/,(T2,A))") &
1403 58 : "MOLSYM| Molecular symmetry information", &
1404 116 : "MOLSYM|"
1405 : WRITE (iw, "(T2,A,T77,A4)") &
1406 58 : "MOLSYM| Point group", ADJUSTR(sym%point_group_symbol)
1407 58 : IF (sym%point_group_order > -1) THEN
1408 : WRITE (iw, "(T2,A,T77,I4)") &
1409 55 : "MOLSYM| Group order ", sym%point_group_order
1410 : END IF
1411 :
1412 58 : IF (MOD(plevel, 10) == 1) THEN
1413 : WRITE (iw, "(T2,A)") &
1414 58 : "MOLSYM|", &
1415 116 : "MOLSYM| Groups of atoms equivalent by symmetry"
1416 : WRITE (iw, "(T2,A,T31,A)") &
1417 58 : "MOLSYM| Group number", "Group members (atomic indices)"
1418 164 : DO i = 1, sym%ngroup
1419 106 : nequatom = sym%ulequatom(i) - sym%llequatom(i) + 1
1420 106 : CPASSERT(nequatom > 0)
1421 530 : ALLOCATE (equatomlist(nequatom), idxlist(nequatom))
1422 681 : equatomlist(1:nequatom) = sym%symequ_list(sym%llequatom(i):sym%ulequatom(i))
1423 681 : idxlist = 0
1424 106 : CALL sort(equatomlist, nequatom, idxlist)
1425 : WRITE (iw, "(T2,A,T10,I5,T16,I6,T31,10(1X,I4),/,(T2,'MOLSYM|',T31,10(1X,I4)))") &
1426 106 : "MOLSYM|", i, nequatom, (equatomlist(iequatom), iequatom=1, nequatom)
1427 164 : DEALLOCATE (equatomlist, idxlist)
1428 : END DO
1429 : END IF
1430 :
1431 58 : IF (MOD(plevel/100, 10) == 1) THEN
1432 : ! Print all symmetry elements
1433 : WRITE (iw, "(T2,A,/,T2,A,/,T2,A,T31,A,T45,A,T60,A,T75,A)") &
1434 58 : "MOLSYM|", &
1435 58 : "MOLSYM| Symmetry elements", &
1436 116 : "MOLSYM| Element number", "Type", "X", "Y", "Z"
1437 58 : secount = 1
1438 : WRITE (iw, "(T2,A,T12,I5,T19,I5,T32,A1,T36,3(1X,F14.8))") &
1439 58 : "MOLSYM|", secount, secount, "E", 0.0_dp, 0.0_dp, 0.0_dp
1440 : ! Mirror planes
1441 58 : string = "@ "
1442 211 : DO isig = 1, sym%nsig
1443 153 : secount = secount + 1
1444 153 : CALL outse(sym%sig(:, isig), sym%eps_geo)
1445 : WRITE (iw, "(T2,A,T12,I5,T19,I5,T32,A3,T36,3(1X,F14.8))") &
1446 670 : "MOLSYM|", secount, isig, string, (sym%sig(i, isig), i=1, 3)
1447 : END DO
1448 : ! C axes
1449 252 : DO icn = 2, sym%ncn
1450 194 : IF (icn < 10) THEN
1451 194 : WRITE (string, "(A1,I1)") "C", icn
1452 : ELSE
1453 0 : WRITE (string, "(A1,I2)") "C", icn
1454 : END IF
1455 548 : DO isec = 1, sym%nsec(icn)
1456 296 : secount = secount + 1
1457 296 : CALL outse(sym%sec(:, isec, icn), sym%eps_geo)
1458 : WRITE (iw, "(T2,A,T12,I5,T19,I5,T32,A3,T36,3(1X,F14.8))") &
1459 1378 : "MOLSYM|", secount, isec, string, (sym%sec(i, isec, icn), i=1, 3)
1460 : END DO
1461 : END DO
1462 : ! S axes
1463 215 : DO isn = 2, sym%nsn
1464 157 : IF (isn == 2) THEN
1465 29 : WRITE (string, "(A3)") "i "
1466 128 : ELSE IF (isn < 10) THEN
1467 111 : WRITE (string, "(A1,I1)") "S", isn
1468 : ELSE
1469 17 : WRITE (string, "(A1,I2)") "S", isn
1470 : END IF
1471 288 : DO ises = 1, sym%nses(isn)
1472 73 : secount = secount + 1
1473 73 : CALL outse(sym%ses(:, ises, isn), sym%eps_geo)
1474 : WRITE (iw, "(T2,A,T12,I5,T19,I5,T32,A3,T36,3(1X,F14.8))") &
1475 449 : "MOLSYM|", secount, ises, string, (sym%ses(i, ises, icn), i=1, 3)
1476 : END DO
1477 : END DO
1478 : END IF
1479 :
1480 58 : IF (MOD(plevel/10, 10) == 1 .AND. SIZE(coord, 2) > 1) THEN
1481 : ! Print the moments of the molecular inertia tensor
1482 : WRITE (iw, "(T2,A,/,T2,A,/,T2,A,T43,A,T58,A,T73,A)") &
1483 57 : "MOLSYM|", &
1484 57 : "MOLSYM| Moments of the molecular inertia tensor [a.u.]", &
1485 114 : "MOLSYM|", "I(x)", "I(y)", "I(z)"
1486 57 : string = "xyz"
1487 741 : IF (MAXVAL(sym%tenmat) >= formatmaxval) THEN
1488 0 : DO i = 1, 3
1489 : WRITE (iw, "(T2,A,T32,A,T36,3(1X,ES14.4))") &
1490 0 : "MOLSYM|", "I("//string(i:i)//")", (sym%tenmat(i, j), j=1, 3)
1491 : END DO
1492 : ELSE
1493 228 : DO i = 1, 3
1494 : WRITE (iw, "(T2,A,T32,A,T36,3(1X,F14.8))") &
1495 741 : "MOLSYM|", "I("//string(i:i)//")", (sym%tenmat(i, j), j=1, 3)
1496 : END DO
1497 : END IF
1498 : WRITE (iw, "(T2,A)") &
1499 57 : "MOLSYM|", &
1500 114 : "MOLSYM| Principal moments and axes of inertia [a.u.]"
1501 741 : IF (MAXVAL(sym%tenmat) >= formatmaxval) THEN
1502 : WRITE (iw, "(T2,A,T36,3(1X,ES14.4))") &
1503 0 : "MOLSYM|", (sym%tenval(i), i=1, 3)
1504 : ELSE
1505 : WRITE (iw, "(T2,A,T36,3(1X,F14.8))") &
1506 228 : "MOLSYM|", (sym%tenval(i), i=1, 3)
1507 : END IF
1508 228 : DO i = 1, 3
1509 : WRITE (iw, "(T2,A,T32,A,T36,3(1X,F14.8))") &
1510 741 : "MOLSYM|", string(i:i), (sym%tenvec(i, j), j=1, 3)
1511 : END DO
1512 : END IF
1513 :
1514 58 : IF (MOD(plevel, 10) == 1) THEN
1515 : ! Print the Cartesian coordinates of the standard orientation
1516 58 : CALL write_coordinates(coord, atype, element, z, weight, iw)
1517 : END IF
1518 :
1519 58 : END SUBROUTINE print_symmetry
1520 :
1521 : ! **************************************************************************************************
1522 : !> \brief Rotate the molecule about an axis defined by the vector a. The
1523 : !> rotation angle is phi (radians).
1524 : !> \param phi ...
1525 : !> \param a ...
1526 : !> \param sym ...
1527 : !> \param coord ...
1528 : !> \par History
1529 : !> Creation (16.11.98, Matthias Krack)
1530 : !> \author jgh
1531 : ! **************************************************************************************************
1532 87 : SUBROUTINE rotate_molecule(phi, a, sym, coord)
1533 : REAL(KIND=dp), INTENT(IN) :: phi
1534 : REAL(KIND=dp), DIMENSION(3), INTENT(IN) :: a
1535 : TYPE(molsym_type), INTENT(inout) :: sym
1536 : REAL(KIND=dp), DIMENSION(:, :), INTENT(inout) :: coord
1537 :
1538 : INTEGER :: i
1539 : REAL(KIND=dp) :: length_of_a
1540 :
1541 : ! Check the length of vector a
1542 87 : length_of_a = SQRT(a(1)*a(1) + a(2)*a(2) + a(3)*a(3))
1543 87 : IF (length_of_a > sym%eps_geo) THEN
1544 :
1545 : ! Build up the rotation matrix
1546 42 : CALL build_rotmat(phi, a(:), sym%rotmat(:, :))
1547 :
1548 : ! Rotate the molecule by phi around a
1549 10689 : coord(:, :) = MATMUL(sym%rotmat(:, :), coord(:, :))
1550 :
1551 : ! Rotate the normal vectors of the mirror planes which are already found
1552 3339 : sym%sig(:, 1:sym%nsig) = MATMUL(sym%rotmat(:, :), sym%sig(:, 1:sym%nsig))
1553 :
1554 : ! Rotate the Cn axes which are already found
1555 186 : DO i = 2, sym%ncn
1556 9696 : sym%sec(:, 1:sym%nsec(i), i) = MATMUL(sym%rotmat(:, :), sym%sec(:, 1:sym%nsec(i), i))
1557 : END DO
1558 :
1559 : ! Rotate the Sn axes which are already found
1560 134 : DO i = 2, sym%nsn
1561 3014 : sym%ses(:, 1:sym%nses(i), i) = MATMUL(sym%rotmat(:, :), sym%ses(:, 1:sym%nses(i), i))
1562 : END DO
1563 :
1564 : ! Store current rotation
1565 2688 : sym%inptostd(:, :) = MATMUL(sym%rotmat(:, :), sym%inptostd(:, :))
1566 :
1567 : END IF
1568 :
1569 87 : END SUBROUTINE rotate_molecule
1570 :
1571 : ! **************************************************************************************************
1572 : !> \brief Rotate the molecule about a n-fold axis defined by the vector a
1573 : !> using a rotation angle of 2*pi/n. Check, if the axis is a Cn axis.
1574 : !> \param n ...
1575 : !> \param a ...
1576 : !> \param sym ...
1577 : !> \param coord ...
1578 : !> \return ...
1579 : !> \par History
1580 : !> Creation (16.11.98, Matthias Krack)
1581 : !> \author jgh
1582 : ! **************************************************************************************************
1583 6344 : FUNCTION saxis(n, a, sym, coord)
1584 : INTEGER, INTENT(IN) :: n
1585 : REAL(KIND=dp), DIMENSION(3), INTENT(IN) :: a
1586 : TYPE(molsym_type), INTENT(inout) :: sym
1587 : REAL(KIND=dp), DIMENSION(:, :), INTENT(inout) :: coord
1588 : LOGICAL :: saxis
1589 :
1590 : INTEGER :: iatom, natoms
1591 : REAL(KIND=dp) :: length_of_a, phi
1592 : REAL(KIND=dp), DIMENSION(3) :: b
1593 :
1594 6344 : saxis = .FALSE.
1595 :
1596 6344 : length_of_a = SQRT(a(1)*a(1) + a(2)*a(2) + a(3)*a(3))
1597 :
1598 6344 : natoms = SIZE(coord, 2)
1599 :
1600 : ! Check the length of the axis vector a
1601 6344 : IF (length_of_a > sym%eps_geo) THEN
1602 : ! Calculate the rotation angle phi and build up the rotation matrix rotmat
1603 6344 : phi = 2.0_dp*pi/REAL(n, KIND=dp)
1604 6344 : CALL build_rotmat(phi, a(:), sym%rotmat(:, :))
1605 : ! Check all atoms
1606 9816 : DO iatom = 1, natoms
1607 : ! Rotate the actual atom by 2*pi/n about a
1608 124111 : b(:) = MATMUL(sym%rotmat(:, :), coord(:, iatom))
1609 : ! Reflect the coordinates of the rotated atom
1610 38188 : b(:) = reflect_vector(b(:), a(:))
1611 : ! Check if the coordinates of the rotated atom are in the coordinate set of the molecule
1612 9816 : IF (equatom(iatom, b(:), sym, coord) == 0) RETURN
1613 : END DO
1614 : ! The axis defined by a is a Sn axis, thus return with saxis = .TRUE.
1615 : saxis = .TRUE.
1616 : END IF
1617 :
1618 : END FUNCTION saxis
1619 :
1620 : ! **************************************************************************************************
1621 : !> \brief Reflect all atoms of the molecule through a mirror plane defined
1622 : !> by the normal vector a.
1623 : !> \param a ...
1624 : !> \param sym ...
1625 : !> \param coord ...
1626 : !> \return ...
1627 : !> \par History
1628 : !> Creation (16.11.98, Matthias Krack)
1629 : !> \author jgh
1630 : ! **************************************************************************************************
1631 2808 : FUNCTION sigma(a, sym, coord)
1632 : REAL(KIND=dp), DIMENSION(3), INTENT(IN) :: a
1633 : TYPE(molsym_type), INTENT(inout) :: sym
1634 : REAL(KIND=dp), DIMENSION(:, :), INTENT(inout) :: coord
1635 : LOGICAL :: sigma
1636 :
1637 : INTEGER :: iatom, natoms
1638 : REAL(KIND=dp) :: length_of_a
1639 : REAL(KIND=dp), DIMENSION(3) :: b
1640 :
1641 2808 : sigma = .FALSE.
1642 :
1643 2808 : length_of_a = SQRT(a(1)*a(1) + a(2)*a(2) + a(3)*a(3))
1644 :
1645 : ! Check the length of vector a
1646 2808 : IF (length_of_a > sym%eps_geo) THEN
1647 2674 : natoms = SIZE(coord, 2)
1648 10068 : DO iatom = 1, natoms
1649 : ! Reflect the actual atom
1650 9471 : b(:) = reflect_vector(coord(:, iatom), a(:))
1651 : ! Check if the coordinates of the reflected atom are in the coordinate set of the molecule
1652 10068 : IF (equatom(iatom, b(:), sym, coord) == 0) RETURN
1653 : END DO
1654 : ! The plane defined by a is a mirror plane, thus return with sigma = .TRUE.
1655 : sigma = .TRUE.
1656 : END IF
1657 :
1658 : END FUNCTION sigma
1659 :
1660 : ! **************************************************************************************************
1661 : !> \brief Calculate the molecular tensor of inertia and the vector to the
1662 : !> center of mass of the molecule.
1663 : !> \param sym ...
1664 : !> \param coord ...
1665 : !> \par History
1666 : !> Creation (16.11.98, Matthias Krack)
1667 : !> \author jgh
1668 : ! **************************************************************************************************
1669 57 : SUBROUTINE tensor(sym, coord)
1670 : TYPE(molsym_type), INTENT(inout) :: sym
1671 : REAL(KIND=dp), DIMENSION(:, :), INTENT(inout) :: coord
1672 :
1673 : INTEGER :: natoms
1674 : REAL(KIND=dp) :: tt
1675 :
1676 : ! Find the vector center_of_mass to molecular center of mass
1677 57 : natoms = SIZE(coord, 2)
1678 3269 : sym%center_of_mass(:) = MATMUL(coord(:, 1:natoms), sym%aw(1:natoms))/SUM(sym%aw(1:natoms))
1679 :
1680 : ! Translate the center of mass of the molecule to the origin
1681 2353 : coord(:, 1:natoms) = coord(:, 1:natoms) - SPREAD(sym%center_of_mass(:), 2, natoms)
1682 :
1683 : ! Build up the molecular tensor of inertia
1684 :
1685 631 : sym%tenmat(1, 1) = DOT_PRODUCT(sym%aw(1:natoms), (coord(2, 1:natoms)**2 + coord(3, 1:natoms)**2))
1686 631 : sym%tenmat(2, 2) = DOT_PRODUCT(sym%aw(1:natoms), (coord(3, 1:natoms)**2 + coord(1, 1:natoms)**2))
1687 631 : sym%tenmat(3, 3) = DOT_PRODUCT(sym%aw(1:natoms), (coord(1, 1:natoms)**2 + coord(2, 1:natoms)**2))
1688 :
1689 631 : sym%tenmat(1, 2) = -DOT_PRODUCT(sym%aw(1:natoms), (coord(1, 1:natoms)*coord(2, 1:natoms)))
1690 631 : sym%tenmat(1, 3) = -DOT_PRODUCT(sym%aw(1:natoms), (coord(1, 1:natoms)*coord(3, 1:natoms)))
1691 631 : sym%tenmat(2, 3) = -DOT_PRODUCT(sym%aw(1:natoms), (coord(2, 1:natoms)*coord(3, 1:natoms)))
1692 :
1693 : ! Symmetrize tensor matrix
1694 57 : sym%tenmat(2, 1) = sym%tenmat(1, 2)
1695 57 : sym%tenmat(3, 1) = sym%tenmat(1, 3)
1696 57 : sym%tenmat(3, 2) = sym%tenmat(2, 3)
1697 :
1698 : ! Diagonalize the molecular tensor of inertia
1699 57 : CALL jacobi(sym%tenmat(:, :), sym%tenval(:), sym%tenvec(:, :))
1700 :
1701 : ! Secure that the principal axes are right-handed
1702 228 : sym%tenvec(:, 3) = vector_product(sym%tenvec(:, 1), sym%tenvec(:, 2))
1703 :
1704 57 : tt = SQRT(sym%tenval(1)**2 + sym%tenval(2)**2 + sym%tenval(3)**2)
1705 57 : CPASSERT(tt /= 0)
1706 :
1707 57 : END SUBROUTINE tensor
1708 :
1709 : ! **************************************************************************************************
1710 : !> \brief Transformation the Cartesian coodinates with the matrix tenvec.
1711 : !> The coordinate axes can be switched according to the index
1712 : !> vector idx. If idx(1) is equal to 3 instead to 1, then the first
1713 : !> eigenvector (smallest eigenvalue) of the molecular tensor of
1714 : !> inertia is connected to the z axis instead of the x axis. In
1715 : !> addition the local atomic coordinate systems are initialized,
1716 : !> if the symmetry is turned off.
1717 : !> \param sym ...
1718 : !> \param coord ...
1719 : !> \param idx ...
1720 : !> \par History
1721 : !> Creation (16.11.98, Matthias Krack)
1722 : !> \author jgh
1723 : ! **************************************************************************************************
1724 50 : SUBROUTINE tracar(sym, coord, idx)
1725 : TYPE(molsym_type), INTENT(inout) :: sym
1726 : REAL(KIND=dp), DIMENSION(:, :), INTENT(inout) :: coord
1727 : INTEGER, DIMENSION(3), INTENT(IN) :: idx
1728 :
1729 : INTEGER :: iatom, natom
1730 : REAL(KIND=dp), DIMENSION(3, 3) :: tenvec
1731 :
1732 : ! Transformation of the atomic coordinates ***
1733 50 : natom = SIZE(coord, 2)
1734 650 : tenvec = TRANSPOSE(sym%tenvec)
1735 482 : DO iatom = 1, natom
1736 3074 : coord(idx, iatom) = MATMUL(tenvec, coord(:, iatom))
1737 : END DO
1738 :
1739 : ! Modify the transformation matrix for input orientation -> standard orientation
1740 650 : sym%inptostd(idx, :) = tenvec
1741 :
1742 50 : END SUBROUTINE tracar
1743 :
1744 : ! **************************************************************************************************
1745 : !> \brief Distance between two points
1746 : !> \param a ...
1747 : !> \param b ...
1748 : !> \return ...
1749 : !> \author jgh
1750 : ! **************************************************************************************************
1751 2864 : FUNCTION dist(a, b) RESULT(d)
1752 : REAL(KIND=dp), DIMENSION(3), INTENT(IN) :: a, b
1753 : REAL(KIND=dp) :: d
1754 :
1755 11456 : d = SQRT(SUM((a - b)**2))
1756 :
1757 2864 : END FUNCTION
1758 : ! **************************************************************************************************
1759 : !> \brief Write atomic coordinates to output unit iw.
1760 : !> \param coord ...
1761 : !> \param atype ...
1762 : !> \param element ...
1763 : !> \param z ...
1764 : !> \param weight ...
1765 : !> \param iw ...
1766 : !> \date 08.05.2008
1767 : !> \author JGH
1768 : ! **************************************************************************************************
1769 58 : SUBROUTINE write_coordinates(coord, atype, element, z, weight, iw)
1770 : REAL(KIND=dp), DIMENSION(:, :), INTENT(in) :: coord
1771 : INTEGER, DIMENSION(:), INTENT(in) :: atype
1772 : CHARACTER(LEN=*), DIMENSION(:), INTENT(in) :: element
1773 : INTEGER, DIMENSION(:), INTENT(in) :: z
1774 : REAL(KIND=dp), DIMENSION(:), INTENT(in) :: weight
1775 : INTEGER, INTENT(IN) :: iw
1776 :
1777 : INTEGER :: iatom, natom
1778 :
1779 58 : IF (iw > 0) THEN
1780 58 : natom = SIZE(coord, 2)
1781 : WRITE (UNIT=iw, FMT="(T2,A)") &
1782 58 : "MOLSYM|", &
1783 116 : "MOLSYM| Atomic coordinates of the standard orientation in BOHR"
1784 : WRITE (UNIT=iw, FMT="(T2,A,T37,A,T51,A,T65,A,T77,A)") &
1785 58 : "MOLSYM| Atom Kind Element", "X", "Y", "Z", "Mass"
1786 633 : DO iatom = 1, natom
1787 : WRITE (UNIT=iw, FMT="(T2,A,I7,1X,I4,1X,A2,1X,I4,3(1X,F13.8),1X,F9.4)") &
1788 575 : "MOLSYM|", iatom, atype(iatom), ADJUSTL(element(iatom)), z(iatom), &
1789 1208 : coord(1:3, iatom), weight(iatom)
1790 : END DO
1791 : WRITE (UNIT=iw, FMT="(T2,A)") &
1792 58 : "MOLSYM|", &
1793 116 : "MOLSYM| Atomic coordinates of the standard orientation in ANGSTROM"
1794 : WRITE (UNIT=iw, FMT="(T2,A,T37,A,T51,A,T65,A,T77,A)") &
1795 58 : "MOLSYM| Atom Kind Element", "X", "Y", "Z", "Mass"
1796 633 : DO iatom = 1, natom
1797 : WRITE (UNIT=iw, FMT="(T2,A,I7,1X,I4,1X,A2,1X,I4,3(1X,F13.8),1X,F9.4)") &
1798 575 : "MOLSYM|", iatom, atype(iatom), ADJUSTL(element(iatom)), z(iatom), &
1799 2933 : coord(1:3, iatom)*angstrom, weight(iatom)
1800 : END DO
1801 : END IF
1802 :
1803 58 : END SUBROUTINE write_coordinates
1804 :
1805 0 : END MODULE molsym
|