@@ -56,18 +56,31 @@ def parallel_vectors(v1, v2, tol=TOLERANCE):
56
56
cos = numpy .dot (_normalize (v1 ), _normalize (v2 ))
57
57
return (abs (cos - 1 ) < TOLERANCE ) | (abs (cos + 1 ) < TOLERANCE )
58
58
59
- def argsort_coords (coords , decimals = None ):
59
+ def argsort_coords (coords , decimals = None , tol = 0.05 ):
60
+ # * np.round for decimal places can lead to more errors than the actual
61
+ # difference between two numbers. For example,
62
+ # np.round([0.1249999999,0.1250000001], 2) => [0.12, 0.13]
63
+ # np.round([0.1249999999,0.1250000001], 3) => [0.125, 0.125]
64
+ # When loosen tolerance is used, compared to the more strict tolerance,
65
+ # the coordinates might look more different.
66
+ # * Using the power of two as the factor can reduce such errors, although not
67
+ # faithfully rounding to the required decimals.
68
+ # * For normal molecules, tol~=0.1 in coordinates is enough to distinguish
69
+ # atoms in molecule. A very tight threshold is not appropriate here. With
70
+ # tight threshold, small differences in coordinates may lead to different
71
+ # arg orders.
60
72
if decimals is None :
61
- decimals = int (- numpy .log10 (TOLERANCE )) - 1
62
- coords = numpy .around (coords , decimals = decimals )
73
+ fac = 2 ** int (- numpy .log2 (tol ))
74
+ else :
75
+ fac = 2 ** int (3.3219281 * decimals )
76
+ # +.5 for rounding to the nearest integer
77
+ coords = (coords * fac + .5 ).astype (int )
63
78
idx = numpy .lexsort ((coords [:,2 ], coords [:,1 ], coords [:,0 ]))
64
79
return idx
65
80
66
- def sort_coords (coords , decimals = None ):
67
- if decimals is None :
68
- decimals = int (- numpy .log10 (TOLERANCE )) - 1
81
+ def sort_coords (coords , decimals = None , tol = 0.05 ):
69
82
coords = numpy .asarray (coords )
70
- idx = argsort_coords (coords , decimals = decimals )
83
+ idx = argsort_coords (coords , tol = tol )
71
84
return coords [idx ]
72
85
73
86
# ref. http://en.wikipedia.org/wiki/Rotation_matrix
@@ -482,7 +495,11 @@ def symm_identical_atoms(gpname, atoms):
482
495
newc = numpy .dot (coords , op )
483
496
idx = argsort_coords (newc )
484
497
if not numpy .allclose (coords0 , newc [idx ], atol = TOLERANCE ):
485
- raise PointGroupSymmetryError ('Symmetry identical atoms not found' )
498
+ raise PointGroupSymmetryError (
499
+ 'Symmetry identical atoms not found. This may be due to '
500
+ 'the strict setting of the threshold symm.geom.TOLERANCE. '
501
+ 'Consider adjusting the tolerance.' )
502
+
486
503
dup_atom_ids .append (idx )
487
504
488
505
dup_atom_ids = numpy .sort (dup_atom_ids , axis = 0 ).T
@@ -524,6 +541,14 @@ def check_symm(gpname, atoms, basis=None):
524
541
opdic = symm_ops (gpname )
525
542
ops = [opdic [op ] for op in OPERATOR_TABLE [gpname ]]
526
543
rawsys = SymmSys (atoms , basis )
544
+
545
+ # A fast check using Casimir tensors
546
+ coords = rawsys .atoms [:,1 :]
547
+ weights = rawsys .atoms [:,0 ]
548
+ for op in ops :
549
+ if not is_identical_geometry (coords , coords .dot (op ), weights ):
550
+ return False
551
+
527
552
for lst in rawsys .atomtypes .values ():
528
553
coords = rawsys .atoms [lst ,1 :]
529
554
idx = argsort_coords (coords )
@@ -543,6 +568,27 @@ def shift_atom(atoms, orig, axis):
543
568
c = numpy .dot (c - orig , numpy .array (axis ).T )
544
569
return [[atoms [i ][0 ], c [i ]] for i in range (len (atoms ))]
545
570
571
+ def is_identical_geometry (coords1 , coords2 , weights ):
572
+ '''A fast check to compare the geometry of two molecules using Casimir tensors'''
573
+ if coords1 .shape != coords2 .shape :
574
+ return False
575
+ for order in range (1 , 4 ):
576
+ if abs (casimir_tensors (coords1 , weights , order ) -
577
+ casimir_tensors (coords2 , weights , order )).max () > TOLERANCE :
578
+ return False
579
+ return True
580
+
581
+ def casimir_tensors (r , q , order = 1 ):
582
+ if order == 1 :
583
+ return q .dot (r )
584
+ elif order == 2 :
585
+ return numpy .einsum ('i,ix,iy->xy' , q , r , r )
586
+ elif order == 3 :
587
+ return numpy .einsum ('i,ix,iy,iz->xyz' , q , r , r , r )
588
+ else :
589
+ raise NotImplementedError
590
+
591
+
546
592
class RotationAxisNotFound (PointGroupSymmetryError ):
547
593
pass
548
594
0 commit comments