(git:374b731)
Loading...
Searching...
No Matches
graphcon.F
Go to the documentation of this file.
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 uses a combination of graphs and hashing to determine if two molecules
10!> are topologically equivalent, and if so, finds the one by one mapping
11!> \note
12!> the graph isomorphism being solved is a computationally hard one
13!> and can not be solved in polynomial time in the general case
14!> http://mathworld.wolfram.com/IsomorphicGraphs.html
15!> the problem arises if many atoms are topologically equivalent
16!> the current algorithm is able to solve the problem for benzene (C6H6)
17!> but not for a fullerene (C60). Large systems are not really a problem (JAC).
18!> as almost all atoms are topologically unique.
19!> \par History
20!> 09.2006 [Joost VandeVondele]
21!> \author Joost VandeVondele
22! **************************************************************************************************
24
25 USE util, ONLY: sort
26#include "./base/base_uses.f90"
27
28 IMPLICIT NONE
29
30 PRIVATE
32
33 ! a molecule is an array of vertices, each vertex has a kind
34 ! and a list of edges (bonds).
35 ! (the number is the index of the other vertex in the array that builds the molecule)
36! **************************************************************************************************
38 TYPE(vertex), POINTER, DIMENSION(:) :: graph
39 END TYPE graph_type
40
41! **************************************************************************************************
42 TYPE vertex
43 INTEGER :: kind
44 INTEGER, POINTER, DIMENSION(:) :: bonds
45 END TYPE vertex
46
47! **************************************************************************************************
48 TYPE class
49 INTEGER, DIMENSION(:), POINTER :: reference
50 INTEGER, DIMENSION(:), POINTER :: unordered
51 INTEGER :: kind
52 INTEGER :: nele
53 LOGICAL :: first
54 INTEGER, DIMENSION(:), POINTER :: order
55 INTEGER, DIMENSION(:), POINTER :: q
56 END TYPE class
57
58! **************************************************************************************************
59 TYPE superclass
60 INTEGER :: nele
61 INTEGER, DIMENSION(:), POINTER :: classes
62 END TYPE
63
64CONTAINS
65
66! **************************************************************************************************
67!> \brief hashes a molecule to a number. Molecules that are the (topologically) the same
68!> have the same hash. However, there is a small chance that molecules with the same hash
69!> are different
70!> \param reference IN : molecule with atomic kinds and bonds
71!> \param kind_ref OUT : an atomic hash which is the same for topologically equivalent atoms
72!> \param hash OUT : a hash which is the same for topologically equivalent molecules
73!> \par History
74!> 09.2006 created [Joost VandeVondele]
75!> \note
76!> Although relatively fast in general, might be quadratic with molecule size for
77!> some systems (e.g. linear alkanes)
78! **************************************************************************************************
79 SUBROUTINE hash_molecule(reference, kind_ref, hash)
80 TYPE(vertex), DIMENSION(:), INTENT(IN) :: reference
81 INTEGER, DIMENSION(:), INTENT(OUT) :: kind_ref
82 INTEGER, INTENT(OUT) :: hash
83
84 INTEGER :: i, ihash, n, nclasses, nclasses_old, &
85 old_class
86 INTEGER, ALLOCATABLE, DIMENSION(:) :: index, kind_new
87
88 n = SIZE(kind_ref)
89 ALLOCATE (kind_new(n), index(n))
90 kind_ref = reference%kind
91 nclasses_old = 0
92 DO ihash = 1, n
93 ! generate a hash based on the the kind of each atom and the kind of its bonded atoms
94 DO i = 1, n
95 kind_new(i) = hash_kind(kind_ref(i), kind_ref(reference(i)%bonds))
96 END DO
97 kind_ref = kind_new
98 ! find the number of equivalent atoms
99 CALL sort(kind_new, n, index)
100 nclasses = 1
101 old_class = kind_new(1)
102 DO i = 2, n
103 IF (kind_new(i) .NE. old_class) THEN
104 nclasses = nclasses + 1
105 old_class = kind_new(i)
106 END IF
107 END DO
108 ! if we have not generated new classes, we have presumably found all equivalence classes
109 IF (nclasses == nclasses_old) EXIT
110 nclasses_old = nclasses
111 ! write(*,*) "Classes",Ihash, Nclasses
112 END DO
113 ! hash (sorted) kinds to a molecular hash
114 hash = joaat_hash_i(kind_new)
115 DEALLOCATE (kind_new, index)
116 END SUBROUTINE hash_molecule
117
118! **************************************************************************************************
119!> \brief If two molecules are topologically the same, finds the ordering that maps
120!> the unordered one on the ordered one.
121!> \param reference molecular description (see type definition)
122!> \param unordered molecular description (see type definition)
123!> \param order the mapping reference=order(unordred) if matches=.TRUE.
124!> undefined if matches=.FALSE.
125!> \param matches .TRUE. = the ordering was found
126!> \par History
127!> 09.2006 created [Joost VandeVondele]
128!> \note
129!> See not at the top of the file about why this algorithm might consider
130!> molecules with a large number of equivalent atoms as different
131!> despite the fact that an ordering could exist for which they are the same
132! **************************************************************************************************
133 SUBROUTINE reorder_graph(reference, unordered, order, matches)
134 TYPE(vertex), DIMENSION(:), INTENT(IN) :: reference, unordered
135 INTEGER, DIMENSION(:), INTENT(OUT) :: order
136 LOGICAL, INTENT(OUT) :: matches
137
138 INTEGER, PARAMETER :: max_tries = 1000000
139
140 INTEGER :: hash_re, hash_un, i, iclass, iele, &
141 isuperclass, itries, j, n, nclasses, &
142 nele, old_class
143 INTEGER, ALLOCATABLE, DIMENSION(:) :: class_of_atom, index_ref, index_un, &
144 kind_ref, kind_ref_ordered, kind_un, &
145 kind_un_ordered, superclass_of_atom
146 TYPE(class), ALLOCATABLE, DIMENSION(:) :: classes
147 TYPE(superclass), ALLOCATABLE, DIMENSION(:) :: superclasses
148
149! allows for worst case matching of two benzenes ... (6!)*(6!)/6=86400
150! with some margin for other molecules
151! molecules with no symmetry e.g. JAC need less than 500 tries
152! catch the cases where the molecules are trivially different
153
154 IF (SIZE(reference) .NE. SIZE(unordered)) THEN
155 matches = .false.
156 RETURN
157 END IF
158
159 ! catch the case where the molecules are already in the right order
160 n = SIZE(order)
161 order = (/(i, i=1, n)/)
162 IF (matrix_equal(reference, unordered, order)) THEN
163 matches = .true.
164 RETURN
165 END IF
166
167 ! determine the kind of each atom, and the hash of the whole molecule
168 ALLOCATE (kind_ref(n), kind_un(n), index_ref(n), index_un(n), &
169 kind_ref_ordered(n), kind_un_ordered(n), &
170 class_of_atom(n), superclass_of_atom(n))
171 CALL hash_molecule(reference, kind_ref, hash_re)
172 CALL hash_molecule(unordered, kind_un, hash_un)
173 IF (hash_re .NE. hash_un) THEN
174 matches = .false.
175 RETURN
176 END IF
177
178 ! generate the classes of equivalent atoms, i.e. the groups of atoms of the same topological kind
179 kind_ref_ordered(:) = kind_ref
180 CALL sort(kind_ref_ordered, n, index_ref)
181 kind_un_ordered(:) = kind_un
182 CALL sort(kind_un_ordered, n, index_un)
183 IF (any(kind_ref_ordered .NE. kind_un_ordered)) THEN
184 matches = .false.
185 RETURN
186 END IF
187
188 ! count different classes, assign their kinds, and the number of elements
189 nclasses = 1
190 old_class = kind_ref_ordered(1)
191 DO i = 2, n
192 IF (kind_ref_ordered(i) .NE. old_class) THEN
193 nclasses = nclasses + 1
194 old_class = kind_ref_ordered(i)
195 END IF
196 END DO
197 ALLOCATE (classes(nclasses))
198 classes(1)%kind = kind_ref_ordered(1)
199 nclasses = 1
200 classes(1)%Nele = 1
201 DO i = 2, n
202 IF (kind_ref_ordered(i) .NE. classes(nclasses)%kind) THEN
203 nclasses = nclasses + 1
204 classes(nclasses)%kind = kind_ref_ordered(i)
205 classes(nclasses)%Nele = 1
206 ELSE
207 classes(nclasses)%Nele = classes(nclasses)%Nele + 1
208 END IF
209 END DO
210
211 ! assign the atoms to their classes
212 iele = 0
213 DO i = 1, nclasses
214 nele = classes(i)%Nele
215 ALLOCATE (classes(i)%reference(nele))
216 ALLOCATE (classes(i)%unordered(nele))
217 DO j = 1, nele
218 iele = iele + 1
219 classes(i)%reference(j) = index_ref(iele)
220 classes(i)%unordered(j) = index_un(iele)
221 END DO
222 class_of_atom(classes(i)%reference) = i
223 ALLOCATE (classes(i)%order(nele))
224 ALLOCATE (classes(i)%q(nele))
225 classes(i)%order = (/(j, j=1, nele)/)
226 classes(i)%first = .true.
227 END DO
228
229 ! find which groups of classes (superclasses) that can be solved independently.
230 ! only classes with more than one element that are connected need to be reordered simultaniously
231
232 ! find these connected components in a recursive way
233 superclass_of_atom = -1
234 isuperclass = 0
235 DO i = 1, n
236 ! this atom belongs to a class with several equivalent atoms, and has not yet been found
237 IF (superclass_of_atom(i) .EQ. -1 .AND. classes(class_of_atom(i))%Nele > 1) THEN
238 isuperclass = isuperclass + 1
239 CALL spread_superclass(i, isuperclass, superclass_of_atom, class_of_atom, classes, reference)
240 END IF
241 END DO
242
243 ! put classes into superclasses
244 ALLOCATE (superclasses(isuperclass))
245 superclasses%Nele = 0
246 DO i = 1, nclasses
247 j = superclass_of_atom(classes(i)%reference(1))
248 IF (j > 0) superclasses(j)%Nele = superclasses(j)%Nele + 1
249 END DO
250 DO i = 1, isuperclass
251 ALLOCATE (superclasses(i)%classes(superclasses(i)%Nele))
252 superclasses(i)%Nele = 0
253 END DO
254 DO i = 1, nclasses
255 j = superclass_of_atom(classes(i)%reference(1))
256 IF (j > 0) THEN
257 superclasses(j)%Nele = superclasses(j)%Nele + 1
258 superclasses(j)%classes(superclasses(j)%Nele) = i
259 END IF
260 END DO
261
262 ! write(*,*) "Class generation time",t2-t1
263 ! WRITE(*,*) "Nclasses, max size, total-non-1 ",Nclasses,MAXVAL(classes%Nele),COUNT(classes%Nele>1)
264 ! write(*,*) "isuperclass ",isuperclass
265
266 ! assign the order array to their initial value
267 DO iclass = 1, nclasses
268 order(classes(iclass)%unordered) = classes(iclass)%reference(classes(iclass)%order)
269 END DO
270
271 ! reorder the atoms superclass after superclass
272 itries = 0
273 DO i = 1, isuperclass
274 DO
275 itries = itries + 1
276
277 ! assign the current order
278 DO iclass = 1, superclasses(i)%Nele
279 j = superclasses(i)%classes(iclass)
280 order(classes(j)%unordered) = classes(j)%reference(classes(j)%order)
281 END DO
282
283 ! check for matches within this superclass only, be happy if we have a match
284 matches = matrix_superclass_equal(reference, unordered, order, superclasses(i), classes)
285 IF (itries > max_tries) THEN
286 WRITE (*, *) "Could not find the 1-to-1 mapping to prove graph isomorphism"
287 WRITE (*, *) "Reordering failed, assuming these molecules are different"
288 EXIT
289 END IF
290 IF (matches) EXIT
291
292 ! generate next permutation within this superclass
293 DO iclass = 1, superclasses(i)%Nele
294 j = superclasses(i)%classes(iclass)
295 CALL all_permutations(classes(j)%order, classes(j)%Nele, &
296 classes(j)%q, classes(j)%first)
297 IF (.NOT. classes(j)%first) EXIT
298 END DO
299
300 ! we are back at the original permutation so we're unable to match this superclass.
301 IF (iclass .EQ. superclasses(i)%Nele .AND. &
302 classes(superclasses(i)%classes(superclasses(i)%Nele))%first) EXIT
303 END DO
304 ! failed in this superblock, can exit now
305 IF (.NOT. matches) EXIT
306 END DO
307
308 ! the final check, just to be sure
309 matches = matrix_equal(reference, unordered, order)
310
311 DO iclass = 1, nclasses
312 DEALLOCATE (classes(iclass)%reference)
313 DEALLOCATE (classes(iclass)%unordered)
314 DEALLOCATE (classes(iclass)%order)
315 DEALLOCATE (classes(iclass)%q)
316 END DO
317 DEALLOCATE (classes)
318 DO i = 1, isuperclass
319 DEALLOCATE (superclasses(i)%classes)
320 END DO
321 DEALLOCATE (superclasses)
322 END SUBROUTINE reorder_graph
323
324! **************************************************************************************************
325!> \brief spreads the superclass over all atoms of this class and all their bonded atoms
326!> provided that the latter belong to a class which contains more than one element
327!> \param I ...
328!> \param isuperclass ...
329!> \param superclass_of_atom ...
330!> \param class_of_atom ...
331!> \param classes ...
332!> \param reference ...
333!> \par History
334!> 09.2006 created [Joost VandeVondele]
335! **************************************************************************************************
336 RECURSIVE SUBROUTINE spread_superclass(I, isuperclass, superclass_of_atom, class_of_atom, &
337 classes, reference)
338 INTEGER, INTENT(IN) :: i, isuperclass
339 INTEGER, DIMENSION(:), INTENT(INOUT) :: superclass_of_atom
340 INTEGER, DIMENSION(:), INTENT(IN) :: class_of_atom
341 TYPE(class), DIMENSION(:), INTENT(IN) :: classes
342 TYPE(vertex), DIMENSION(:), INTENT(IN) :: reference
343
344 INTEGER :: j
345
346 IF (superclass_of_atom(i) .EQ. -1 .AND. classes(class_of_atom(i))%Nele > 1) THEN
347 superclass_of_atom(i) = isuperclass
348 DO j = 1, classes(class_of_atom(i))%Nele
349 CALL spread_superclass(classes(class_of_atom(i))%reference(j), isuperclass, &
350 superclass_of_atom, class_of_atom, classes, reference)
351 END DO
352 DO j = 1, SIZE(reference(i)%bonds)
353 CALL spread_superclass(reference(i)%bonds(j), isuperclass, &
354 superclass_of_atom, class_of_atom, classes, reference)
355 END DO
356 END IF
357 END SUBROUTINE spread_superclass
358
359! **************************************************************************************************
360!> \brief determines of the vertices of this superclass have the same edges
361!> \param reference ...
362!> \param unordered ...
363!> \param order ...
364!> \param super ...
365!> \param classes ...
366!> \return ...
367!> \par History
368!> 09.2006 created [Joost VandeVondele]
369! **************************************************************************************************
370 FUNCTION matrix_superclass_equal(reference, unordered, order, super, classes) RESULT(res)
371 TYPE(vertex), DIMENSION(:), INTENT(IN) :: reference, unordered
372 INTEGER, DIMENSION(:), INTENT(IN) :: order
373 TYPE(superclass), INTENT(IN) :: super
374 TYPE(class), DIMENSION(:), INTENT(IN) :: classes
375 LOGICAL :: res
376
377 INTEGER :: i, iclass, iele, j
378
379! I is the atom in the unordered set
380
381 loop: DO iclass = 1, super%Nele
382 DO iele = 1, classes(super%classes(iclass))%Nele
383 i = classes(super%classes(iclass))%unordered(iele)
384 res = (reference(order(i))%kind == unordered(i)%kind .AND. &
385 SIZE(reference(order(i))%bonds) == SIZE(unordered(i)%bonds))
386 IF (res) THEN
387 DO j = 1, SIZE(reference(order(i))%bonds)
388 IF (all(reference(order(i))%bonds(:) .NE. order(unordered(i)%bonds(j)))) THEN
389 res = .false.
390 EXIT loop
391 END IF
392 END DO
393 ELSE
394 EXIT loop
395 END IF
396 END DO
397 END DO loop
398 END FUNCTION matrix_superclass_equal
399
400! **************************************************************************************************
401!> \brief determines of the vertices of the full set is equal, i.e.
402!> we have the same connectivity graph
403!> \param reference ...
404!> \param unordered ...
405!> \param order ...
406!> \return ...
407!> \par History
408!> 09.2006 created [Joost VandeVondele]
409! **************************************************************************************************
410 FUNCTION matrix_equal(reference, unordered, order) RESULT(res)
411 TYPE(vertex), DIMENSION(:), INTENT(IN) :: reference, unordered
412 INTEGER, DIMENSION(:), INTENT(IN) :: order
413 LOGICAL :: res
414
415 INTEGER :: i, j
416
417 loop: DO i = 1, SIZE(reference)
418 res = (reference(order(i))%kind == unordered(i)%kind .AND. &
419 SIZE(reference(order(i))%bonds) == SIZE(unordered(i)%bonds))
420 IF (res) THEN
421 DO j = 1, SIZE(reference(order(i))%bonds)
422 IF (all(reference(order(i))%bonds(:) .NE. order(unordered(i)%bonds(j)))) THEN
423 res = .false.
424 EXIT loop
425 END IF
426 END DO
427 ELSE
428 EXIT loop
429 END IF
430 END DO loop
431 END FUNCTION matrix_equal
432
433! **************************************************************************************************
434!> \brief creates a hash for an atom based on its own kind and on the kinds
435!> of its bonded neighbors
436!> \param me ...
437!> \param bonds ...
438!> \return ...
439!> \par History
440!> 09.2006 created [Joost VandeVondele]
441!> \note
442!> bonds are sorted so that the order of neighbors appearing in the bonded list
443!> is not important
444! **************************************************************************************************
445 FUNCTION hash_kind(me, bonds) RESULT(res)
446 INTEGER, INTENT(IN) :: me
447 INTEGER, DIMENSION(:), INTENT(IN) :: bonds
448 INTEGER :: res
449
450 INTEGER :: i, n
451 INTEGER, ALLOCATABLE, DIMENSION(:) :: index, ordered_bonds
452
453 n = SIZE(bonds)
454 ALLOCATE (ordered_bonds(n + 1), index(n))
455 DO i = 1, n
456 ordered_bonds(i) = bonds(i)
457 END DO
458 ordered_bonds(n + 1) = me
459 ! N: only sort the bonds, not me
460 CALL sort(ordered_bonds, n, index)
461 res = joaat_hash_i(ordered_bonds)
462 END FUNCTION hash_kind
463
464! **************************************************************************************************
465!> \brief generates the hash of an array of integers and the index in the table
466!> \param key an integer array of any length
467!> \return ...
468!> \par History
469!> 09.2006 created [Joost VandeVondele]
470!> \note
471!> http://en.wikipedia.org/wiki/Hash_table
472!> http://www.burtleburtle.net/bob/hash/doobs.html
473!> However, since fortran doesn't have an unsigned 4 byte int
474!> we compute it using an integer with the appropriate range
475!> we return already the index in the table as a final result
476! **************************************************************************************************
477 FUNCTION joaat_hash_i(key) RESULT(hash_index)
478 INTEGER, DIMENSION(:), INTENT(IN) :: key
479 INTEGER :: hash_index
480
481 INTEGER, PARAMETER :: k64 = selected_int_kind(10)
482 INTEGER(KIND=k64), PARAMETER :: b32 = 2_k64**32 - 1_k64
483
484 INTEGER :: i
485 INTEGER(KIND=k64) :: hash
486
487 hash = 0_k64
488 DO i = 1, SIZE(key)
489 hash = iand(hash + ibits(key(i), 0, 8), b32)
490 hash = iand(hash + iand(ishft(hash, 10), b32), b32)
491 hash = iand(ieor(hash, iand(ishft(hash, -6), b32)), b32)
492 hash = iand(hash + ibits(key(i), 8, 8), b32)
493 hash = iand(hash + iand(ishft(hash, 10), b32), b32)
494 hash = iand(ieor(hash, iand(ishft(hash, -6), b32)), b32)
495 hash = iand(hash + ibits(key(i), 16, 8), b32)
496 hash = iand(hash + iand(ishft(hash, 10), b32), b32)
497 hash = iand(ieor(hash, iand(ishft(hash, -6), b32)), b32)
498 hash = iand(hash + ibits(key(i), 24, 8), b32)
499 hash = iand(hash + iand(ishft(hash, 10), b32), b32)
500 hash = iand(ieor(hash, iand(ishft(hash, -6), b32)), b32)
501 END DO
502 hash = iand(hash + iand(ishft(hash, 3), b32), b32)
503 hash = iand(ieor(hash, iand(ishft(hash, -11), b32)), b32)
504 hash = iand(hash + iand(ishft(hash, 15), b32), b32)
505 ! hash is the real 32bit hash value of the string,
506 ! hash_index is an index in the hash_table
507 hash_index = int(mod(hash, int(huge(hash_index), kind=k64)), kind=kind(hash_index))
508 END FUNCTION joaat_hash_i
509
510!===ACM Algorithm 323, Generation of Permutations in Lexicographic
511! Order (G6) by R. J. Ord-Smith, CACM 11 (Feb. 1968):117
512! Original Algorithm modified via Certification by I.M. Leitch,
513! 17 March 1969.
514! Algol to Fortran 77 by H.D.Knoble <hdkLESS at SPAM psu dot edu>,
515! May 1995.
516! x = initial values (/1...n/), first=.TRUE.
517! q = scratch
518! first = .TRUE. if you're back at the original order
519! **************************************************************************************************
520!> \brief ...
521!> \param x ...
522!> \param n ...
523!> \param q ...
524!> \param first ...
525! **************************************************************************************************
526 SUBROUTINE all_permutations(x, n, q, first)
527 INTEGER :: n, x(n), q(n)
528 LOGICAL :: first
529
530 INTEGER :: k, m, t
531
532 IF (n == 1) RETURN
533 IF (first) THEN
534 first = .false.
535 DO m = 1, n - 1
536 q(m) = n
537 END DO
538 END IF
539 IF (q(n - 1) .EQ. n) THEN
540 q(n - 1) = n - 1
541 t = x(n)
542 x(n) = x(n - 1)
543 x(n - 1) = t
544 RETURN
545 END IF
546 DO k = n - 1, 1, -1
547 IF (q(k) .EQ. k) THEN
548 q(k) = n
549 ELSE
550 go to 1
551 END IF
552 END DO
553 first = .true.
554 k = 1
555 GOTO 2
5561 m = q(k)
557 t = x(m)
558 x(m) = x(k)
559 x(k) = t
560 q(k) = m - 1
561 k = k + 1
5622 m = n
563 t = x(m)
564 x(m) = x(k)
565 x(k) = t
566 m = m - 1
567 k = k + 1
568 DO WHILE (k .LT. m)
569 t = x(m)
570 x(m) = x(k)
571 x(k) = t
572 m = m - 1
573 k = k + 1
574 END DO
575 END SUBROUTINE
576END MODULE graphcon
577
static unsigned int hash(const dbm_task_t task)
Private hash function based on Szudzik's elegant pairing. Using unsigned int to return a positive num...
program graph
Program to Map on grid the hills spawned during a metadynamics run.
Definition graph.F:19
uses a combination of graphs and hashing to determine if two molecules are topologically equivalent,...
Definition graphcon.F:23
subroutine, public hash_molecule(reference, kind_ref, hash)
hashes a molecule to a number. Molecules that are the (topologically) the same have the same hash....
Definition graphcon.F:80
subroutine, public reorder_graph(reference, unordered, order, matches)
If two molecules are topologically the same, finds the ordering that maps the unordered one on the or...
Definition graphcon.F:134
All kind of helpful little routines.
Definition util.F:14