@@ -57,9 +57,19 @@ def parallel_vectors(v1, v2, tol=TOLERANCE):
57
57
return (abs (cos - 1 ) < TOLERANCE ) | (abs (cos + 1 ) < TOLERANCE )
58
58
59
59
def argsort_coords (coords , decimals = None ):
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.124, 0.125]
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.
60
68
if decimals is None :
61
- decimals = int (- numpy .log10 (TOLERANCE )) - 1
62
- coords = numpy .around (coords , decimals = decimals )
69
+ fac = 2 ** int (- numpy .log2 (TOLERANCE )+ .5 )
70
+ else :
71
+ fac = 2 ** int (3.3219281 * decimals + .5 )
72
+ coords = numpy .around (coords * fac )
63
73
idx = numpy .lexsort ((coords [:,2 ], coords [:,1 ], coords [:,0 ]))
64
74
return idx
65
75
@@ -482,7 +492,11 @@ def symm_identical_atoms(gpname, atoms):
482
492
newc = numpy .dot (coords , op )
483
493
idx = argsort_coords (newc )
484
494
if not numpy .allclose (coords0 , newc [idx ], atol = TOLERANCE ):
485
- raise PointGroupSymmetryError ('Symmetry identical atoms not found' )
495
+ raise PointGroupSymmetryError (
496
+ 'Symmetry identical atoms not found. This may be due to '
497
+ 'the strict setting of the threshold symm.geom.TOLERANCE. '
498
+ 'Consider adjusting the tolerance.' )
499
+
486
500
dup_atom_ids .append (idx )
487
501
488
502
dup_atom_ids = numpy .sort (dup_atom_ids , axis = 0 ).T
@@ -521,6 +535,13 @@ def check_symm(gpname, atoms, basis=None):
521
535
opdic = symm_ops (gpname )
522
536
ops = [opdic [op ] for op in OPERATOR_TABLE [gpname ]]
523
537
rawsys = SymmSys (atoms , basis )
538
+
539
+ # A fast check using Casimir tensors
540
+ coords = rawsys .atoms [:,1 :]
541
+ for op in ops :
542
+ if not is_identical_geometry (coords , coords .dot (op ), rawsys .weights ):
543
+ return False
544
+
524
545
for lst in rawsys .atomtypes .values ():
525
546
coords = rawsys .atoms [lst ,1 :]
526
547
idx = argsort_coords (coords )
@@ -540,6 +561,27 @@ def shift_atom(atoms, orig, axis):
540
561
c = numpy .dot (c - orig , numpy .array (axis ).T )
541
562
return [[atoms [i ][0 ], c [i ]] for i in range (len (atoms ))]
542
563
564
+ def is_identical_geometry (coords1 , coords2 , weights ):
565
+ '''A fast check to compare the geometry of two molecules using Casimir tensors'''
566
+ if coords1 .shape != coords2 .shape :
567
+ return False
568
+ for order in range (1 , 4 ):
569
+ if abs (casimir_tensors (coords1 [lst ], weights , order ) -
570
+ casimir_tensors (coords2 [lst ], weights , order )).max () > TOLERANCE :
571
+ return False
572
+ return True
573
+
574
+ def casimir_tensors (r , q , order = 1 ):
575
+ if order == 1 :
576
+ return q .dot (r )
577
+ elif order == 2 :
578
+ return numpy .einsum ('i,ix,iy->xy' , q , r , r )
579
+ elif order == 3 :
580
+ return numpy .einsum ('i,ix,iy,iz->xyz' , q , r , r , r )
581
+ else :
582
+ raise NotImplementedError
583
+
584
+
543
585
class RotationAxisNotFound (PointGroupSymmetryError ):
544
586
pass
545
587
@@ -572,6 +614,7 @@ def __init__(self, atoms, basis=None):
572
614
fake_chgs .append ([mole .charge (ksymb )] * len (lst ))
573
615
coords = numpy .array (numpy .vstack (coords ), dtype = float )
574
616
fake_chgs = numpy .hstack (fake_chgs )
617
+ self .weights = fake_chgs
575
618
self .charge_center = numpy .einsum ('i,ij->j' , fake_chgs , coords )/ fake_chgs .sum ()
576
619
coords = coords - self .charge_center
577
620
0 commit comments