geometric/internal.py
def build_dlc_1(self, xyz):
        """
        Build the delocalized internal coordinates (DLCs) which are linear 
        combinations of the primitive internal coordinates. Each DLC is stored
        as a column in self.Vecs.
        
        After some thought, build_dlc_0 did not implement constraint satisfaction
        in the most correct way. Constraint satisfaction was rather slow and
        the --enforce 0.1 may be passed to improve performance. Rethinking how the
        G matrix is constructed provides a more fundamental solution.

        In the new approach implemented here, constrained primitive ICs (PICs) are 
        first set aside from the rest of the PICs. Next, a G-matrix is constructed
        from the rest of the PICs and diagonalized to form DLCs, called "residual" DLCs. 
        The union of the cPICs and rDLCs forms a generalized set of DLCs, but the
        cPICs are not orthogonal to each other or to the rDLCs.
        
        A set of orthogonal DLCs is constructed by carrying out Gram-Schmidt
        on the generalized set. Orthogonalization is carried out on the cPICs in order.
        Next, orthogonalization is carried out on the rDLCs using a greedy algorithm
        that carries out projection for each cPIC, then keeps the one with the largest
        remaining norm. This way we avoid keeping rDLCs that are almost redundant with
        the cPICs. The longest projected rDLC is added to the set and continued until
        the expected number is reached.

        One special note in orthogonalization is that the "overlap" between internal
        coordinates corresponds to the G matrix element. Thus, for DLCs that's a linear
        combination of PICs, then the overlap is given by:

        v_i * B * B^T * v_j = v_i * G * v_j

        Notes on usage: 1) When this is activated, constraints tend to be satisfied very 
        rapidly even if the current coordinates are very far from the constraint values,
        leading to possible blowing up of the energies. In augment_GH, maximum steps in
        constrained degrees of freedom are restricted to 0.1 a.u./radian for this reason.
        
        2) Although the performance of this method is generally superior to the old method,
        the old method with --enforce 0.1 is more extensively tested and recommended.
        Thus, this method isn't enabled by default but provided as an optional feature.

        Parameters
        ----------
        xyz : np.ndarray
            Flat array containing Cartesian coordinates in atomic units
        """
        click()
        G = self.Prims.GMatrix(xyz)
        nprim = len(self.Prims.Internals)
        cPrimIdx = []
        if self.haveConstraints():
            for ic, c in enumerate(self.Prims.cPrims):
                iPrim = self.Prims.Internals.index(c)
                cPrimIdx.append(iPrim)
        ncon = len(self.Prims.cPrims)
        if cPrimIdx != list(range(ncon)):
            raise RuntimeError("The constraint primitives should be at the start of the list")
        # Form a sub-G-matrix that doesn't include the constrained primitives and diagonalize it to form DLCs.
        Gsub = G[ncon:, ncon:]
        time_G = click()
        L, Q = np.linalg.eigh(Gsub)
        # Sort eigenvalues and eigenvectors in descending order (for cleanliness)
        L = L[::-1]
        Q = Q[:, ::-1]
        time_eig = click()
        # print "Build G: %.3f Eig: %.3f" % (time_G, time_eig)
        # Figure out which eigenvectors from the G submatrix to include
        LargeVals = 0
        LargeIdx = []
        GEigThre = 1e-6
        for ival, value in enumerate(L):
            if np.abs(value) > GEigThre:
                LargeVals += 1
                LargeIdx.append(ival)
        # This is the number of nonredundant DLCs that we expect to have at the end
        Expect = np.sum(np.linalg.eigh(G)[0] > 1e-6)

        if (ncon + len(LargeIdx)) < Expect:
            raise RuntimeError("Expected at least %i delocalized coordinates, but got only %i" % (Expect, ncon + len(LargeIdx)))
        # print("%i atoms (expect %i coordinates); %i/%i singular values are > 1e-6" % (self.na, Expect, LargeVals, len(L)))
        
        # Create "generalized" DLCs where the first six columns are the constrained primitive ICs
        # and the other columns are the DLCs formed from the rest
        self.Vecs = np.zeros((nprim, ncon+LargeVals), dtype=float)
        for i in range(ncon):
            self.Vecs[i, i] = 1.0
        self.Vecs[ncon:, ncon:ncon+LargeVals] = Q[:, LargeIdx]

        # Perform Gram-Schmidt orthogonalization
        def ov(vi, vj):
            return multi_dot([vi, G, vj])
        if self.haveConstraints():
            click()
            V = self.Vecs.copy()
            nv = V.shape[1]
            Vnorms = np.array([np.sqrt(ov(V[:,ic], V[:, ic])) for ic in range(nv)])
            # U holds the Gram-Schmidt orthogonalized DLCs
            U = np.zeros((V.shape[0], Expect), dtype=float)
            Unorms = np.zeros(Expect, dtype=float)
            
            for ic in range(ncon):
                # At the top of the loop, V columns are orthogonal to U columns up to ic.
                # Copy V column corresponding to the next constraint to U.
                U[:, ic] = V[:, ic].copy()
                ui = U[:, ic]
                Unorms[ic] = np.sqrt(ov(ui, ui))
                if Unorms[ic]/Vnorms[ic] < 0.1:
                    logger.warning("Constraint %i is almost redundant; after projection norm is %.3f of original\n" % (ic, Unorms[ic]/Vnorms[ic]))
                V0 = V.copy()
                # Project out newest U column from all remaining V columns.
                for jc in range(ic+1, nv):
                    vj = V[:, jc]
                    vj -= ui * ov(ui, vj)/Unorms[ic]**2
                
            for ic in range(ncon, Expect):
                # Pick out the V column with the largest norm
                norms = np.array([np.sqrt(ov(V[:, jc], V[:, jc])) for jc in range(ncon, nv)])
                imax = ncon+np.argmax(norms)
                # Add this column to U
                U[:, ic] = V[:, imax].copy()
                ui = U[:, ic]
                Unorms[ic] = np.sqrt(ov(ui, ui))
                # Project out the newest U column from all V columns
                for jc in range(ncon, nv):
                    V[:, jc] -= ui * ov(ui, V[:, jc])/Unorms[ic]**2
                
            # self.Vecs contains the linear combination coefficients that are our new DLCs
            self.Vecs = U.copy()
            # Constrained DLCs are on the left of self.Vecs.
            self.cDLC = [i for i in range(len(self.Prims.cPrims))]

        self.Internals = ["Constraint" if i < ncon else "DLC" + " %i" % (i+1) for i in range(self.Vecs.shape[1])]
        # # LPW: Coefficients of DLC's are in each column and DLCs corresponding to constraints should basically be like (0 1 0 0 0 ..)
        # pmat2d(self.Vecs, format='f', precision=2)
        # B = self.Prims.wilsonB(xyz)
        # Bdlc = np.einsum('ji,jk->ik', self.Vecs, B)
        # Gdlc = np.dot(Bdlc, Bdlc.T)
        # # Expect to see a diagonal matrix here
        # print("Gdlc")
        # pmat2d(Gdlc, format='e', precision=2)
        # # Expect to see "large" eigenvalues here (no less than 0.1 ideally)
        # print("L, Q")
        # L, Q = np.linalg.eigh(Gdlc)
        # print(L)

        Build the delocalized internal coordinates (DLCs) which are linear 
        combinations of the primitive internal coordinates. Each DLC is stored
        as a column in self.Vecs.
        
        After some thought, build_dlc_0 did not implement constraint satisfaction
        in the most correct way. Constraint satisfaction was rather slow and
        the --enforce 0.1 may be passed to improve performance. Rethinking how the
        G matrix is constructed provides a more fundamental solution.

        In the new approach implemented here, constrained primitive ICs (PICs) are 
        first set aside from the rest of the PICs. Next, a G-matrix is constructed
        from the rest of the PICs and diagonalized to form DLCs, called "residual" DLCs. 
        The union of the cPICs and rDLCs forms a generalized set of DLCs, but the
        cPICs are not orthogonal to each other or to the rDLCs.
        
        A set of orthogonal DLCs is constructed by carrying out Gram-Schmidt
        on the generalized set. Orthogonalization is carried out on the cPICs in order.
        Next, orthogonalization is carried out on the rDLCs using a greedy algorithm
        that carries out projection for each cPIC, then keeps the one with the largest
        remaining norm. This way we avoid keeping rDLCs that are almost redundant with
        the cPICs. The longest projected rDLC is added to the set and continued until
        the expected number is reached.

        One special note in orthogonalization is that the "overlap" between internal
        coordinates corresponds to the G matrix element. Thus, for DLCs that's a linear
        combination of PICs, then the overlap is given by:

        v_i * B * B^T * v_j = v_i * G * v_j

        Notes on usage: 1) When this is activated, constraints tend to be satisfied very 
        rapidly even if the current coordinates are very far from the constraint values,
        leading to possible blowing up of the energies. In augment_GH, maximum steps in
        constrained degrees of freedom are restricted to 0.1 a.u./radian for this reason.
        
        2) Although the performance of this method is generally superior to the old method,
        the old method with --enforce 0.1 is more extensively tested and recommended.
        Thus, this method isn't enabled by default but provided as an optional feature.

        Parameters
        ----------
        xyz : np.ndarray
            Flat array containing Cartesian coordinates in atomic units
        
Build the delocalized internal coordinates (DLCs) which are linear
combinations of the primitive internal coordinates. Each DLC is stored
as a column in self.Vecs.

After some thought, build_dlc_0 did not implement constraint satisfaction
in the most correct way. Constraint satisfaction was rather slow and
the --enforce 0.1 may be passed to improve performance. Rethinking how the
G matrix is constructed provides a more fundamental solution.

In the new approach implemented here, constrained primitive ICs (PICs) are
first set aside from the rest of the PICs. Next, a G-matrix is constructed
from the rest of the PICs and diagonalized to form DLCs, called "residual" DLCs.
The union of the cPICs and rDLCs forms a generalized set of DLCs, but the
cPICs are not orthogonal to each other or to the rDLCs.

A set of orthogonal DLCs is constructed by carrying out Gram-Schmidt
on the generalized set. Orthogonalization is carried out on the cPICs in order.
Next, orthogonalization is carried out on the rDLCs using a greedy algorithm
that carries out projection for each cPIC, then keeps the one with the largest
remaining norm. This way we avoid keeping rDLCs that are almost redundant with
the cPICs. The longest projected rDLC is added to the set and continued until
the expected number is reached.

One special note in orthogonalization is that the "overlap" between internal
coordinates corresponds to the G matrix element. Thus, for DLCs that's a linear
combination of PICs, then the overlap is given by.



Notes on usage: 1) When this is activated, constraints tend to be satisfied very
rapidly even if the current coordinates are very far from the constraint values,
leading to possible blowing up of the energies. In augment_GH, maximum steps in
constrained degrees of freedom are restricted to 0.1 a.u./radian for this reason.

2) Although the performance of this method is generally superior to the old method,
the old method with --enforce 0.1 is more extensively tested and recommended.
Thus, this method isn't enabled by default but provided as an optional feature.

Parameters

xyz : np.ndarray
Flat array containing Cartesian coordinates in atomic units
Build the delocalized internal coordinates ( DLCs ) which are linear combinations of the primitive internal coordinates . Each DLC is stored as a column in self . Vecs . After some thought build_dlc_0 did not implement constraint satisfaction in the most correct way . Constraint satisfaction was rather slow and the -- enforce 0 . 1 may be passed to improve performance . Rethinking how the G matrix is constructed provides a more fundamental solution . In the new approach implemented here constrained primitive ICs ( PICs ) are first set aside from the rest of the PICs . Next a G - matrix is constructed from the rest of the PICs and diagonalized to form DLCs called " residual " DLCs . The union of the cPICs and rDLCs forms a generalized set of DLCs but the cPICs are not orthogonal to each other or to the rDLCs . A set of orthogonal DLCs is constructed by carrying out Gram - Schmidt on the generalized set . Orthogonalization is carried out on the cPICs in order . Next orthogonalization is carried out on the rDLCs using a greedy algorithm that carries out projection for each cPIC then keeps the one with the largest remaining norm . This way we avoid keeping rDLCs that are almost redundant with the cPICs . The longest projected rDLC is added to the set and continued until the expected number is reached . One special note in orthogonalization is that the " overlap " between internal coordinates corresponds to the G matrix element . Thus for DLCs that ' s a linear combination of PICs then the overlap is given by . Notes on usage : 1 ) When this is activated constraints tend to be satisfied very rapidly even if the current coordinates are very far from the constraint values leading to possible blowing up of the energies . In augment_GH maximum steps in constrained degrees of freedom are restricted to 0 . 1 a . u . / radian for this reason . 2 ) Although the performance of this method is generally superior to the old method the old method with -- enforce 0 . 1 is more extensively tested and recommended . Thus this method isn ' t enabled by default but provided as an optional feature . Parameters xyz : np . ndarray Flat array containing Cartesian coordinates in atomic units
def build_dlc_1(self, xyz):
        click()
        G = self.Prims.GMatrix(xyz)
        nprim = len(self.Prims.Internals)
        cPrimIdx = []
        if self.haveConstraints():
            for ic, c in enumerate(self.Prims.cPrims):
                iPrim = self.Prims.Internals.index(c)
                cPrimIdx.append(iPrim)
        ncon = len(self.Prims.cPrims)
        if cPrimIdx != list(range(ncon)):
            raise RuntimeError("The constraint primitives should be at the start of the list")
        Gsub = G[ncon:, ncon:]
        time_G = click()
        L, Q = np.linalg.eigh(Gsub)
        L = L[::-1]
        Q = Q[:, ::-1]
        time_eig = click()
        LargeVals = 0
        LargeIdx = []
        GEigThre = 1e-6
        for ival, value in enumerate(L):
            if np.abs(value) > GEigThre:
                LargeVals += 1
                LargeIdx.append(ival)
        Expect = np.sum(np.linalg.eigh(G)[0] > 1e-6)
        if (ncon + len(LargeIdx)) < Expect:
            raise RuntimeError("Expected at least %i delocalized coordinates, but got only %i" % (Expect, ncon + len(LargeIdx)))
        self.Vecs = np.zeros((nprim, ncon+LargeVals), dtype=float)
        for i in range(ncon):
            self.Vecs[i, i] = 1.0
        self.Vecs[ncon:, ncon:ncon+LargeVals] = Q[:, LargeIdx]
        def ov(vi, vj):
            return multi_dot([vi, G, vj])
        if self.haveConstraints():
            click()
            V = self.Vecs.copy()
            nv = V.shape[1]
            Vnorms = np.array([np.sqrt(ov(V[:,ic], V[:, ic])) for ic in range(nv)])
            U = np.zeros((V.shape[0], Expect), dtype=float)
            Unorms = np.zeros(Expect, dtype=float)
            for ic in range(ncon):
                U[:, ic] = V[:, ic].copy()
                ui = U[:, ic]
                Unorms[ic] = np.sqrt(ov(ui, ui))
                if Unorms[ic]/Vnorms[ic] < 0.1:
                    logger.warning("Constraint %i is almost redundant; after projection norm is %.3f of original\n" % (ic, Unorms[ic]/Vnorms[ic]))
                V0 = V.copy()
                for jc in range(ic+1, nv):
                    vj = V[:, jc]
                    vj -= ui * ov(ui, vj)/Unorms[ic]**2
            for ic in range(ncon, Expect):
                norms = np.array([np.sqrt(ov(V[:, jc], V[:, jc])) for jc in range(ncon, nv)])
                imax = ncon+np.argmax(norms)
                U[:, ic] = V[:, imax].copy()
                ui = U[:, ic]
                Unorms[ic] = np.sqrt(ov(ui, ui))
                for jc in range(ncon, nv):
                    V[:, jc] -= ui * ov(ui, V[:, jc])/Unorms[ic]**2
            self.Vecs = U.copy()
            self.cDLC = [i for i in range(len(self.Prims.cPrims))]
        self.Internals = ["Constraint" if i < ncon else "DLC" + " %i" % (i+1) for i in range(self.Vecs.shape[1])]
def build_dlc_1 ( self , xyz ) : click ( ) G = self . Prims . GMatrix ( xyz ) nprim = len ( self . Prims . Internals ) cPrimIdx = [ ] if self . haveConstraints ( ) : for ic , c in enumerate ( self . Prims . cPrims ) : iPrim = self . Prims . Internals . index ( c ) cPrimIdx . append ( iPrim ) ncon = len ( self . Prims . cPrims ) if cPrimIdx != list ( range ( ncon ) ) : raise RuntimeError ( "The constraint primitives should be at the start of the list" ) Gsub = G [ ncon : , ncon : ] time_G = click ( ) L , Q = np . linalg . eigh ( Gsub ) L = L [ : : - 1 ] Q = Q [ : , : : - 1 ] time_eig = click ( ) LargeVals = 0 LargeIdx = [ ] GEigThre = 1e-6 for ival , value in enumerate ( L ) : if np . abs ( value ) > GEigThre : LargeVals += 1 LargeIdx . append ( ival ) Expect = np . sum ( np . linalg . eigh ( G ) [ 0 ] > 1e-6 ) if ( ncon + len ( LargeIdx ) ) < Expect : raise RuntimeError ( "Expected at least %i delocalized coordinates, but got only %i" % ( Expect , ncon + len ( LargeIdx ) ) ) self . Vecs = np . zeros ( ( nprim , ncon + LargeVals ) , dtype = float ) for i in range ( ncon ) : self . Vecs [ i , i ] = 1.0 self . Vecs [ ncon : , ncon : ncon + LargeVals ] = Q [ : , LargeIdx ] def ov ( vi , vj ) : return multi_dot ( [ vi , G , vj ] ) if self . haveConstraints ( ) : click ( ) V = self . Vecs . copy ( ) nv = V . shape [ 1 ] Vnorms = np . array ( [ np . sqrt ( ov ( V [ : , ic ] , V [ : , ic ] ) ) for ic in range ( nv ) ] ) U = np . zeros ( ( V . shape [ 0 ] , Expect ) , dtype = float ) Unorms = np . zeros ( Expect , dtype = float ) for ic in range ( ncon ) : U [ : , ic ] = V [ : , ic ] . copy ( ) ui = U [ : , ic ] Unorms [ ic ] = np . sqrt ( ov ( ui , ui ) ) if Unorms [ ic ] / Vnorms [ ic ] < 0.1 : logger . warning ( "Constraint %i is almost redundant; after projection norm is %.3f of original\n" % ( ic , Unorms [ ic ] / Vnorms [ ic ] ) ) V0 = V . copy ( ) for jc in range ( ic + 1 , nv ) : vj = V [ : , jc ] vj -= ui * ov ( ui , vj ) / Unorms [ ic ] ** 2 for ic in range ( ncon , Expect ) : norms = np . array ( [ np . sqrt ( ov ( V [ : , jc ] , V [ : , jc ] ) ) for jc in range ( ncon , nv ) ] ) imax = ncon + np . argmax ( norms ) U [ : , ic ] = V [ : , imax ] . copy ( ) ui = U [ : , ic ] Unorms [ ic ] = np . sqrt ( ov ( ui , ui ) ) for jc in range ( ncon , nv ) : V [ : , jc ] -= ui * ov ( ui , V [ : , jc ] ) / Unorms [ ic ] ** 2 self . Vecs = U . copy ( ) self . cDLC = [ i for i in range ( len ( self . Prims . cPrims ) ) ] self . Internals = [ "Constraint" if i < ncon else "DLC" + " %i" % ( i + 1 ) for i in range ( self . Vecs . shape [ 1 ] ) ]
Build the delocalized internal coordinates (DLCs) which are linear
combinations of the primitive internal coordinates.
Build the delocalized internal coordinates ( DLCs ) which are linear combinations of the primitive internal coordinates .
"""
        Build the delocalized internal coordinates (DLCs) which are linear 
        combinations of the primitive internal coordinates. Each DLC is stored
        as a column in self.Vecs.
        
        After some thought, build_dlc_0 did not implement constraint satisfaction
        in the most correct way. Constraint satisfaction was rather slow and
        the --enforce 0.1 may be passed to improve performance. Rethinking how the
        G matrix is constructed provides a more fundamental solution.

        In the new approach implemented here, constrained primitive ICs (PICs) are 
        first set aside from the rest of the PICs. Next, a G-matrix is constructed
        from the rest of the PICs and diagonalized to form DLCs, called "residual" DLCs. 
        The union of the cPICs and rDLCs forms a generalized set of DLCs, but the
        cPICs are not orthogonal to each other or to the rDLCs.
        
        A set of orthogonal DLCs is constructed by carrying out Gram-Schmidt
        on the generalized set. Orthogonalization is carried out on the cPICs in order.
        Next, orthogonalization is carried out on the rDLCs using a greedy algorithm
        that carries out projection for each cPIC, then keeps the one with the largest
        remaining norm. This way we avoid keeping rDLCs that are almost redundant with
        the cPICs. The longest projected rDLC is added to the set and continued until
        the expected number is reached.

        One special note in orthogonalization is that the "overlap" between internal
        coordinates corresponds to the G matrix element. Thus, for DLCs that's a linear
        combination of PICs, then the overlap is given by:

        v_i * B * B^T * v_j = v_i * G * v_j

        Notes on usage: 1) When this is activated, constraints tend to be satisfied very 
        rapidly even if the current coordinates are very far from the constraint values,
        leading to possible blowing up of the energies. In augment_GH, maximum steps in
        constrained degrees of freedom are restricted to 0.1 a.u./radian for this reason.
        
        2) Although the performance of this method is generally superior to the old method,
        the old method with --enforce 0.1 is more extensively tested and recommended.
        Thus, this method isn't enabled by default but provided as an optional feature.

        Parameters
        ----------
        xyz : np.ndarray
            Flat array containing Cartesian coordinates in atomic units
        """ # Form a sub-G-matrix that doesn't include the constrained primitives and diagonalize it to form DLCs. # Sort eigenvalues and eigenvectors in descending order (for cleanliness) # print "Build G: %.3f Eig: %.3f" % (time_G, time_eig) # Figure out which eigenvectors from the G submatrix to include # This is the number of nonredundant DLCs that we expect to have at the end # print("%i atoms (expect %i coordinates); %i/%i singular values are > 1e-6" % (self.na, Expect, LargeVals, len(L))) # Create "generalized" DLCs where the first six columns are the constrained primitive ICs # and the other columns are the DLCs formed from the rest # Perform Gram-Schmidt orthogonalization # U holds the Gram-Schmidt orthogonalized DLCs # At the top of the loop, V columns are orthogonal to U columns up to ic. # Copy V column corresponding to the next constraint to U. # Project out newest U column from all remaining V columns. # Pick out the V column with the largest norm # Add this column to U # Project out the newest U column from all V columns # self.Vecs contains the linear combination coefficients that are our new DLCs # Constrained DLCs are on the left of self.Vecs. # # LPW: Coefficients of DLC's are in each column and DLCs corresponding to constraints should basically be like (0 1 0 0 0 ..) # pmat2d(self.Vecs, format='f', precision=2) # B = self.Prims.wilsonB(xyz) # Bdlc = np.einsum('ji,jk->ik', self.Vecs, B) # Gdlc = np.dot(Bdlc, Bdlc.T) # # Expect to see a diagonal matrix here # print("Gdlc") # pmat2d(Gdlc, format='e', precision=2) # # Expect to see "large" eigenvalues here (no less than 0.1 ideally) # print("L, Q") # L, Q = np.linalg.eigh(Gdlc) # print(L)
self
xyz