Ejemplo n.º 1
0
 def getTransitionDipoles(self):
     """
     computes and aligns transition dipoles between all Nst states
     This function should be called only once directly after getScalarCouplings!
     """
     tdip = self.tddftb.getTransitionDipolesExc(self.Nst)
     # eigenvectors can have arbitrary phases
     # align transition dipoles with old ones
     tdip_old = self.tdip_old
     if type(tdip_old) == type(None):
         tdip_old = tdip
     for A in range(0, self.Nst):
         for B in range(0, self.Nst):
             sign = np.dot(tdip_old[A,B,:], tdip[A,B,:])
             if sign < 0.0:
                 tdip[A,B,:] *= -1.0
     self.tdip_old = tdip
     if self.tddftb.dftb2.verbose > 0:
         state_labels = [("S%d" % I) for I in range(0, self.Nst)]
         print("Transition Dipoles")
         print("==================")
         print("X-Component")
         print(utils.annotated_matrix(tdip[:,:,0], state_labels, state_labels))
         print("Y-Component")
         print(utils.annotated_matrix(tdip[:,:,1], state_labels, state_labels))
         print("Z-Component")
         print(utils.annotated_matrix(tdip[:,:,2], state_labels, state_labels))
     #
     return tdip
Ejemplo n.º 2
0
    def getJacobian(self):
        pos = XYZ.atomlist2vector(self.atomlist)
#        pos = self.standard_orientation()
        jac, jac_inv = jacobian_cartesian2internal(self.masses, pos, self.atomlist)
        print("Jacobian  J_ij = d( q_j )/d( x_i )")
        print("=====================================")
        print(utils.annotated_matrix(jac,["x_%s" % i for i in range(0, len(jac[:,0]))], ["q_%s" % j for j in range(0, len(jac[0,:]))]))

        print("Jacobian  J^(-1)_ij = d( x_j )/d( q_i )")
        print("=====================================")
#        jac_inv = inv(jac)
        print(utils.annotated_matrix(jac_inv,["q_%s" % i for i in range(0, len(jac_inv[:,0]))], ["x_%s" % j for j in range(0, len(jac_inv[0,:]))]))


#        print("Jacobian  J^(-1)_ij = d( x_j )/d( q_i )")
#        print("=====================================")
#        jac_inv = inv(jac)
#        print(utils.annotated_matrix(jac_inv,["q_%s" % i for i in range(0, len(pos))], ["x_%s" % j for j in range(0, len(pos))]))

        return jac, jac_inv
Ejemplo n.º 3
0
    def _table_gradients(self, g_cart, g_intern):
        """
        make tables with gradient vectors in cartesian and internal coordinates
        """
        txt = ""
        txt += " =========================== \n"
        txt += " Cartesian Gradient dE/dx(i) \n"
        txt += " =========================== \n"
        txt += utils.annotated_matrix(np.reshape(g_cart, (len(g_cart), 1)),
                                      self.cartesian_labels, ["grad E"])
        txt += " cartesian gradient norm : %e \n" % la.norm(g_cart)
        txt += "\n"

        txt += " ========================== \n"
        txt += " Internal Gradient dE/dq(i)  \n"
        txt += " ========================== \n"
        txt += utils.annotated_matrix(np.reshape(g_intern, (len(g_intern), 1)),
                                      self.internal_labels, ["grad E"])
        txt += " internal gradient norm : %e \n" % la.norm(g_intern)
        txt += "\n"

        return txt
Ejemplo n.º 4
0
    def getJacobian(self, h=1.0e-10):
        """
        numerically find Jacobian matrix for transformation from internal to cartesian coordinates

        J_ij = d( x_i )/d( chi_j )

        where x_1,x_2,...,x_3N are the cartesian coordinates of N atoms
        and chi_1,chi_2,...,chi_3N are the internal coordinates

        Parameters:
        ===========
        h: step for difference quotient

        Returns:
        ========
        3Nx3N numpy array with J_ij
        """
        pos = [array(posi) for (Zi,posi) in self.atomlist]
        x = _lst2vec(pos)
        ipos = cartesian2internal(pos)
        Jacobian = zeros((3*len(pos),3*len(pos)))
        for j in range(0, 3*len(pos)): # iterate over atoms
            # symmetric difference quotient
            chiplus = _lst2vec(ipos)
            chiplus[j] += h
            chiminus = _lst2vec(ipos)
            chiminus[j] -= h

            xplus = _lst2vec(internal2cartesian(_vec2lst(chiplus)))
            xminus = _lst2vec(internal2cartesian(_vec2lst(chiminus)))
            Jacobian[:,j] = (xplus - xminus) / (2*h)
            # forward difference quotient
            # Jacobian[:,j] = (xplus - x) / h

        print "Jacobian:"
        from DFTB.utils import annotated_matrix
        print annotated_matrix(Jacobian,["x_%s" % i for i in range(0, 3*len(pos))], ["q_%s" % j for j in range(0, 3*len(pos))])
        return Jacobian
Ejemplo n.º 5
0
    def _table_Bmatrix(self, B):
        """
        make table with rows of Wilson's B-matrix belonging to the
        active internal coordinates
        """

        txt  = " ======================================================================\n"
        txt += " Wilson's B-matrix B(i,j) = dq(i)/dx(j) for active internal coordinates\n"
        txt += "     B - bond,   A - valence angle,   D - dihedral,   I - inversion    \n"
        txt += " ======================================================================\n"
 
        txt += utils.annotated_matrix(B, self.internal_labels, self.cartesian_labels)
        txt += "\n"

        return txt
    print "Linear polymer consists of %d monomeric units" % nunits

    for I in range(0, Nst_poly):
        PI = Ptrans_poly[I]

        #        plt.matshow(PI)

        print "Analyse state %d of polymer" % I
        MI = np.zeros((Nst_mono, nunits))
        for n in range(0, nunits):
            # extract diagonal block of the n-th subunit
            Pn = PI[n * nAOm:(n + 1) * nAOm, n * nAOm:(n + 1) * nAOm]
            vvec = Pn.flatten()

            for Im in range(0, Nst_mono):
                bvec = Ptrans_mono[Im].flatten()
                MI[Im, n] = np.dot(bvec, vvec)

        unit_labels = ["U-%d" % nu for nu in range(0, nunits)]
        state_labels = ["St-%d" % st for st in range(0, Nst_mono)]
        print utils.annotated_matrix(MI, state_labels, unit_labels)
        plt.matshow(MI, interpolation="nearest", cmap=plt.cm.jet)
        #plt.show()
    plt.show()
    """
    plt.matshow(Ptrans_mono[0], interpolation="nearest", cmap=plt.cm.jet)
    for Ptrans in Ptrans_poly:
        plt.matshow(Ptrans, interpolation="nearest", cmap=plt.cm.jet)
    plt.show()
    """
def Gouterman_matelems():
    """
    computes overlap and hamiltonian matrix elements between the 4 frontier orbitals (a1u,a2u,eg,eg)
    of neighbouring porphyrine squares in a porphyrine flake (polynomino).
    The matrix elements between vertically and horizontally fused porphyrins will be related
    by a rotation of 90 degrees and will be different.
    
    Returns:
    ========:
    orbe: energies of the four orbitals
    Sh,Sv: two 4x4 matrices with overlaps between frontier orbitals of 
        horizontally and vertically fused porphyrins.
    H0h,H0v: two 4x4 matrices with matrix elements of the 0th-order DFTB hamiltonian 
        (neglecting interaction between partial charges) 
    """
    # perform DFTB calculation on the monomer to obtain the MO coefficients for the frontier
    # orbitals.
    lat, unitcell, meso, beta = zn_porphene_lattice(substitutions=[])
    monomer = unitcell + meso + beta

    dftb = DFTB2(monomer)
    dftb.setGeometry(monomer)
    dftb.getEnergy()
    orbs = dftb.getKSCoefficients()
    orbe = dftb.getKSEnergies()
    H**O, LUMO = dftb.getFrontierOrbitals()
    # In DFTB the orbital symmetries are:
    #   H-2  a1u
    #   H-1  rubbish
    #   H    a2u
    #   L    eg
    #   L+1  eg
    # The H-1 belongs to the sigma-framework and has the wrong energetic position in DFTB.
    Gouterman_orbitals = np.array([H**O - 2, H**O, LUMO, LUMO + 1],
                                  dtype=int)  # indeces of G. orbitals
    # remove orbitals belonging to hydrogen
    valorbs = dftb.getValorbs()
    mu = 0  # iterates over all orbitals
    hydrogen_orbs = [
    ]  # indeces of orbitals belonging to hydrogen that should be removed
    for i, (Zi, posi) in enumerate(monomer):
        for (ni, li, mi) in valorbs[Zi]:
            if (Zi == 1):
                hydrogen_orbs.append(mu)
            mu += 1
    # remove rows or columns belonging to hydrogen
    orbs = np.delete(orbs, hydrogen_orbs, 0)
    orbs = np.delete(orbs, hydrogen_orbs, 1)
    nmo, nmo = orbs.shape

    a1, a2, a3 = lat.getLatticeVectors()
    C = orbs[:, Gouterman_orbitals]
    # energies of Gouterman orbitals
    orbe = orbe[Gouterman_orbitals]
    # horizontally fused
    # matrix elements between AOs
    H0h, Sh = dftb._constructH0andS_AB(nmo, nmo, unitcell,
                                       shift_atomlist(unitcell, a1))
    # transform to the basis of frontier orbitals
    Sh = np.dot(C.transpose(), np.dot(Sh, C))
    H0h = np.dot(C.transpose(), np.dot(H0h, C))
    # vertically fused
    # matrix elements between AOs
    H0v, Sv = dftb._constructH0andS_AB(nmo, nmo, unitcell,
                                       shift_atomlist(unitcell, a2))
    # transform to the basis of frontier orbitals
    Sv = np.dot(C.transpose(), np.dot(Sv, C))
    H0v = np.dot(C.transpose(), np.dot(H0v, C))

    #
    orb_names = ["a1u", "a2u", "eg", "eg"]

    print "Lattice vectors"
    print "   a1 = %s bohr" % a1
    print "   a2 = %s bohr" % a2
    print "orbital energies:"
    for name, en in zip(orb_names, orbe * AtomicData.hartree_to_eV):
        print "  energy(%3s) = %8.4f eV" % (name, en)
    print "matrix elements with neighbouring porphyrin"
    print "along lattice vector a1 = %s" % a1
    print "overlap Sh"
    print utils.annotated_matrix(Sh, orb_names, orb_names)
    print "hamiltonian H0h"
    print utils.annotated_matrix(H0h, orb_names, orb_names)
    print "along lattice vector a2 = %s" % a2
    print "overlap Sv"
    print utils.annotated_matrix(Sv, orb_names, orb_names)
    print "hamiltonian H0v"
    print utils.annotated_matrix(H0v, orb_names, orb_names)

    return orbe, Sh, Sv, H0h, H0v
def asymptotic_Ylm(continuum_orbs,
                   energy,
                   rmax=2500.0,
                   npts_r=300,
                   lebedev_order=65):
    """
    decompose continuum orbitals asymptotically into spherical harmonics
                          a
        wfn    = sum     R  (r)  Y (th,ph)
           a        L,M   L,M     L,M

          a       /rmax+2pi/k  2   |  a   |2
        C      =  |           r dr | R    |
          L,M     /rmax            |  L,M |

    Parameters
    ----------
    continuum_orbitals : list of continuum orbitals
    energy             : photokinetic energy of continuum orbitals, E = 1/2 k^2

    Optional
    --------
    rmax               : radius at which the molecular potential is indistinguishable from the asymptotic potential
    npts_r             : number of points for integrating over radial interval [rmax,rmax+2pi/k]
    lebedev_order      : integer, order of Lebedev grid

    Returns
    -------
    Cs                 : Cs[i,a] is the contribution of spherical harmonic Y_(Li,Mi) to orbital a
                         at large distance
    LMs                : list of tuples (L,M) in the same order as in the 1st axis of `Cs`.
    """
    # wave number for kinetic energy E=1/2 k^2
    k = np.sqrt(2 * energy)
    # wavelength
    wavelength = 2.0 * np.pi / k

    # radial grid
    # sample points and weights for Gauss-Legendre quadrature on the interval [-1,1]
    leggauss_pts, leggauss_weights = legendre.leggauss(npts_r)
    # For Gaussian quadrature the integral [-1,1] has to be changed into [rmax,rmax+2pi/k].
    # new endpoints of interval
    a = rmax
    b = rmax + wavelength
    # transformed sampling points and weights
    r = 0.5 * (b - a) * leggauss_pts + 0.5 * (a + b)
    dr = 0.5 * (b - a) * leggauss_weights
    # Lebedev grid for spherical wave expansion
    th, ph, weights_angular = get_lebedev_grid(lebedev_order)
    # The lebedev quadrature rule is
    #         /
    #  I[f] = | dOmega f(Omega) = 4 pi sum  weights_angular[i] * f(th[i],ph[i])
    #         /                           i
    # For convencience we multiply the factor of 4*pi into the weights
    weights_angular *= 4 * np.pi

    # evaluate spherical harmonics for L=0,1,2,3 M=-L,..,L on angular Lebedev grid
    Lmax = 3
    Ys, LMs = spherical_harmonics_vec(th, ph, Lmax)
    Ys = np.array(Ys)
    # cartesian coordinates of radial and angular grids
    x = outerN(r, np.sin(th) * np.cos(ph))
    y = outerN(r, np.sin(th) * np.sin(ph))
    z = outerN(r, np.cos(th))

    # differential for r-integration
    r2dr = r**2 * dr
    # volume element   r^2 dr dOmega
    dV = outerN(r2dr, weights_angular)
    # add r-axis to angular weights
    dOmega = outerN(np.ones(npts_r), weights_angular)

    # nc: number of continuum wavefunctions
    nc = len(continuum_orbs)
    # number of angular components (L,M) for Lmax=2
    LMdim = Lmax * (Lmax + 2) + 1
    assert LMdim == len(LMs)
    # set up array for expansion coefficients
    Cs = np.zeros((LMdim, nc))

    for a in range(0, nc):  # loop over continuum orbitals
        wfn_a = continuum_orbs[a].amp(x, y, z)
        # normalization constant
        nrm2 = np.sum(dV * abs(wfn_a)**2)
        wfn_a /= np.sqrt(nrm2)

        for iLM, (l, m) in enumerate(LMs):
            # add r-axis to spherical harmonics Y_(l,m)(r,th,ph) = Y_(l,m)(th,ph)
            Ylm = outerN(np.ones(npts_r), Ys[iLM])
            # radial wavefunction of continuum orbital belonging to L,M channel
            #   a       /pi           /2pi      *
            # R (r)  =  | sin(th) dth | dph  Y  (th,ph)  wfn (r,th,ph)
            #   L,M     /0            /0       L,M           a
            #
            #                    a
            # wfn_a = sum_(L,M) R (r)  Y (th,ph)
            #                    L,M    L,M

            # integrate over angles
            Rlm = np.sum(dOmega * Ylm.conjugate() * wfn_a, axis=1)
            # integrate over r
            #  a     /rmax+k/2pi  2    |   a  |2
            # C    = |           r  dr | R    |  1 / (4 pi)
            #  L,M   /rmax             |  L,M |
            Cs[iLM, a] = np.sum(r2dr * abs(Rlm)**2)

    print "  Asymptotic Decomposition Y_(l,m)"
    print "  ================================"
    row_labels = ["orb. %2.1d" % a for a in range(0, nc)]
    col_labels = ["Y(%d,%+d)" % (l, m) for (l, m) in LMs]
    txt = annotated_matrix(Cs.transpose(), row_labels, col_labels)
    print txt

    return Cs, LMs
def h2_ion_averaged_pad_scan(energy_range,
                             data_file,
                             npts_r=60,
                             rmax=300.0,
                             lebedev_order=23,
                             radial_grid_factor=3,
                             units="eV-Mb",
                             tdip_threshold=1.0e-5):
    """
    compute the photoelectron angular distribution for an ensemble of istropically
    oriented H2+ ions.

    Parameters
    ----------
    energy_range    : numpy array with photoelectron kinetic energies (PKE)
                      for which the PAD should be calculated
    data_file       : path to file, a table with PKE, SIGMA and BETA is written

    Optional
    --------
    npts_r          : number of radial grid points for integration on interval [rmax,rmax+2pi/k]
    rmax            : large radius at which the continuum orbitals can be matched 
                      with the asymptotic solution
    lebedev_order   : order of Lebedev grid for angular integrals
    radial_grid_factor 
                    : factor by which the number of grid points is increased
                      for integration on the interval [0,+inf]
    units           : units for energies and photoionization cross section in output, 'eV-Mb' (eV and Megabarn) or 'a.u.'
    tdip_threshold  : continuum orbitals |f> are neglected if their transition dipole moments mu = |<i|r|f>| to the
                      initial orbital |i> are below this threshold.
    """
    print ""
    print "*******************************************"
    print "*  PHOTOELECTRON ANGULAR DISTRIBUTIONS    *"
    print "*******************************************"
    print ""
    # bond length in bohr
    R = 2.0
    # two protons
    Za = 1.0
    Zb = 1.0
    atomlist = [(1, (0, 0, -R / 2.0)), (1, (0, 0, +R / 2.0))]

    # determine the radius of the sphere where the angular distribution is calculated. It should be
    # much larger than the extent of the molecule
    (xmin, xmax), (ymin, ymax), (zmin, zmax) = Cube.get_bbox(atomlist,
                                                             dbuff=0.0)
    dx, dy, dz = xmax - xmin, ymax - ymin, zmax - zmin
    # increase rmax by the size of the molecule
    rmax += max([dx, dy, dz])
    Npts = max(int(rmax), 1) * 50
    print "Radius of sphere around molecule, rmax = %s bohr" % rmax
    print "Points on radial grid, Npts = %d" % Npts

    # load separation constants or tabulate them if the file 'separation_constants.dat'
    # does not exist
    Lsep = SeparationConstants(R, Za, Zb)
    try:
        Lsep.load_separation_constants()
    except IOError as err:
        nmax = 10
        mmax = 3
        print "generating separation constants..."
        nc2 = 200
        energy_range = np.linspace(-35.0, 20.0, nc2) * 2.0 / R**2
        Lsep.tabulate_separation_constants(energy_range, nmax=nmax, mmax=mmax)
        Lsep.load_separation_constants()

    ### DEBUG
    #Lsep.mmax = 1
    #Lsep.nmax = 1
    ###

    #
    wfn = DimerWavefunctions(R, Za, Zb)
    # compute the bound orbitals without any radial nor angular nodes,
    # the 1sigma_g orbital
    m, n, q = 0, 0, 0
    trig = 'cos'
    energy, (Rfunc1, Sfunc1,
             Pfunc1), wavefunction1 = wfn.getBoundOrbital(m, n, trig, q)
    print "Energy: %s Hartree" % energy
    bound_orbital = WrapperWavefunction(wavefunction1)

    ### DEBUG
    # save cube file for initial bound orbital
    Cube.function_to_cubefile(atomlist,
                              wavefunction1,
                              filename="/tmp/h2+_bound_orbital_%d_%d_%d.cube" %
                              (m, n, q),
                              dbuff=10.0)
    ###

    # compute PADs for all energies
    pad_data = []
    print "  SCAN"

    print "  Writing table with PAD to %s" % data_file
    # table headers
    header = ""
    header += "# npts_r: %s  rmax: %s" % (npts_r, rmax) + '\n'
    header += "# lebedev_order: %s  radial_grid_factor: %s" % (
        lebedev_order, radial_grid_factor) + '\n'
    if units == "eV-Mb":
        header += "# PKE/eV     SIGMA/Mb       BETA2" + '\n'
    else:
        header += "# PKE/Eh     SIGMA/bohr^2   BETA2" + '\n'

    # save table
    access_mode = MPI.MODE_WRONLY | MPI.MODE_CREATE
    fh = MPI.File.Open(comm, data_file, access_mode)
    if rank == 0:
        # only process 0 write the header
        fh.Write_ordered(header)
    else:
        fh.Write_ordered('')

    for i, energy in enumerate(energy_range):
        if i % size == rank:
            print "    PKE = %6.6f Hartree  (%4.4f eV)" % (
                energy, energy * AtomicData.hartree_to_eV)

            # continuum orbitals at a given energy
            continuum_orbitals = []
            # quantum numbers of continuum orbitals (m,n,trig)
            quantum_numbers = []
            phase_shifts = []
            # transition dipoles
            nc = 2 * (Lsep.mmax + 1) * (
                Lsep.nmax +
                1) - 1  # number of continuum orbitals at each energy
            dipoles_RSP = np.zeros((1, nc, 3))
            #
            print "Continuum orbital         m           n                     P                  phase shift (in \pi rad)"
            print "                    # nodes in P                 cos(m*pi) or sin(m*pi)                                "
            print "-------------------------------------------------------------------------------------------------------"
            ic = 0  # enumerate continuum orbitals
            for m in range(0, Lsep.mmax + 1):
                if m > 0:
                    # for m > 0, there are two solutions for P(phi): sin(m*phi) and cos(m*phi)
                    Psolutions = ['cos', 'sin']
                else:
                    Psolutions = ['cos']
                for trig in Psolutions:
                    for n in range(0, Lsep.nmax + 1):
                        Delta, (
                            Rfunc2, Sfunc2,
                            Pfunc2), wavefunction2 = wfn.getContinuumOrbital(
                                m, n, trig, energy)
                        #print "energy= %s eV   m= %d n= %d  phase shift= %s \pi rad" % (energy*AtomicData.hartree_to_eV, m,n,Delta / np.pi)
                        print "      %3.1d             %2.1d        %2.1d                  %s                         %8.6f" % (
                            ic, m, n, trig, Delta)
                        continuum_orbital = WrapperWavefunction(wavefunction2)
                        continuum_orbitals.append(continuum_orbital)
                        quantum_numbers.append((m, n, trig))
                        phase_shifts.append(Delta)
                        ### DEBUG
                        # save cube file for continuum orbital
                        Cube.function_to_cubefile(
                            atomlist,
                            wavefunction2,
                            filename="/tmp/h2+_continuum_orbital_%d_%d_%s.cube"
                            % (m, n, trig),
                            dbuff=10.0)
                        ###

                        # compute transition dipoles exploiting the factorization
                        # of the wavefunction as R(xi)*S(eta)*P(phi). These integrals
                        # are probably more accurate then those using Becke's integration scheme
                        dipoles_RSP[0, ic, :] = wfn.transition_dipoles(
                            Rfunc1, Sfunc1, Pfunc1, Rfunc2, Sfunc2, Pfunc2)
                        ic += 1

            print ""

            # transition dipoles between bound and free orbitals
            dipoles = transition_dipole_integrals(
                atomlist, [bound_orbital],
                continuum_orbitals,
                radial_grid_factor=radial_grid_factor,
                lebedev_order=lebedev_order)
            # Continuum orbitals with vanishing transition dipole moments to the initial orbital
            # do not contribute to the PAD. Filter out those continuum orbitals |f> for which
            # mu^2 = |<i|r|f>|^2  <  threshold
            mu = np.sqrt(np.sum(dipoles[0, :, :]**2, axis=-1))
            dipoles_important = dipoles[0, mu > tdip_threshold, :]
            continuum_orbitals_important = [
                continuum_orbitals[i]
                for i in range(0, len(continuum_orbitals))
                if mu[i] > tdip_threshold
            ]
            quantum_numbers_important = [
                quantum_numbers[i] for i in range(0, len(continuum_orbitals))
                if mu[i] > tdip_threshold
            ]
            phase_shifts_important = [[
                phase_shifts[i]
            ] for i in range(0, len(continuum_orbitals))
                                      if mu[i] > tdip_threshold]

            print " %d of %d continuum orbitals have non-vanishing transition dipoles " \
                % (len(continuum_orbitals_important), len(continuum_orbitals))
            print " to initial orbital (|<i|r|f>| > %e)" % tdip_threshold

            print "  H2+ Quantum Numbers"
            print "  ==================="
            row_labels = [
                "orb. %2.1d" % a
                for a in range(0, len(quantum_numbers_important))
            ]
            col_labels = ["M", "N", "Trig"]
            txt = annotated_matrix(np.array(quantum_numbers_important),
                                   row_labels,
                                   col_labels,
                                   format="%s")
            print txt

            print "  Phase Shifts (in units of pi)"
            print "  ============================="
            row_labels = [
                "orb. %2.1d" % a for a in range(0, len(phase_shifts_important))
            ]
            col_labels = ["Delta"]
            txt = annotated_matrix(np.array(phase_shifts_important) / np.pi,
                                   row_labels,
                                   col_labels,
                                   format="  %8.6f  ")
            print txt

            print "  Transition Dipoles"
            print "  =================="
            print "  threshold = %e" % tdip_threshold
            row_labels = [
                "orb. %2.1d" % a
                for a in range(0, len(quantum_numbers_important))
            ]
            col_labels = ["X", "Y", "Z"]
            txt = annotated_matrix(dipoles_important, row_labels, col_labels)
            print txt

            # compare transition dipoles from two integration methods
            print "check transition dipoles between bound and continuum orbitals"
            ic = 0  # enumerate continuum orbitals
            for m in range(0, Lsep.mmax + 1):
                if m > 0:
                    # for m > 0, there are two solutions for P(phi): sin(m*phi) and cos(m*phi)
                    Psolutions = ['cos', 'sin']
                else:
                    Psolutions = ['cos']
                for trig in Psolutions:
                    for n in range(0, Lsep.nmax + 1):
                        print "continuum orbital %d  (m= %d  n= %d  trig= %s)" % (
                            ic, m, n, trig)
                        print " from factorization R*S*P :  %s" % dipoles_RSP[
                            0, ic, :]
                        print " Becke's integration      :  %s" % dipoles[
                            0, ic, :]

                        ic += 1
            print ""

            #
            Cs, LMs = asymptotic_Ylm(continuum_orbitals_important,
                                     energy,
                                     rmax=rmax,
                                     npts_r=npts_r,
                                     lebedev_order=lebedev_order)

            # expand product of continuum orbitals into spherical harmonics of order L=0,2
            Amo, LMs = angular_product_distribution(
                continuum_orbitals_important,
                energy,
                rmax=rmax,
                npts_r=npts_r,
                lebedev_order=lebedev_order)

            # compute PAD for ionization from orbital 0 (the bound orbital)
            sigma, beta = photoangular_distribution(dipoles_important, Amo,
                                                    LMs, energy)

            if units == "eV-Mb":
                energy *= AtomicData.hartree_to_eV
                # convert cross section sigma from bohr^2 to Mb
                sigma *= AtomicData.bohr2_to_megabarn

            pad_data.append([energy, sigma, beta])
            # save row with PAD for this energy to table
            row = "%10.6f   %10.6e  %+10.6e" % tuple(pad_data[-1]) + '\n'
            fh.Write_ordered(row)

    fh.Close()

    print "  Photoelectron Angular Distribution"
    print "  =================================="
    print "  units: %s" % units
    row_labels = [" " for en in energy_range]
    col_labels = ["energy", "sigma", "beta2"]
    txt = annotated_matrix(np.array(pad_data).real, row_labels, col_labels)
    print txt

    print "FINISHED"
def angular_product_distribution(continuum_orbs,
                                 energy,
                                 rmax=2500.0,
                                 npts_r=300,
                                 lebedev_order=65):
    """
    expand the angular distribution of the product of two molecular orbitals wfn_a(r) and
    wfn_b(r) into spherical harmonics

          a,b     /rmax+2pi/k  2   /pi            /2pi      *            *
        A      =  |           r dr |  sin(th) dth |  dph  Y  (th,ph)  wfn (r,th,ph) wfn (r,th,ph)
          L,M     /rmax            /0             /0       L,M           a             b


    Parameters
    ----------
    continuum_orbitals : list of instances of WrapperWavefunction
    energy             : photokinetic energy of continuum orbitals, E = 1/2 k^2

    Optional
    --------
    rmax               : radius at which the molecular potential is indistinguishable from the asymptotic potential
    npts_r             : number of points for integrating over radial interval [rmax,rmax+2pi/k]
    lebedev_order      : integer, order of Lebedev grid

    Returns
    -------
    Amo            : 3d numpy array with angular components for each pair of molecular orbitals
                                    a,b
                     Amo[LM,a,b] = A
                                    L,M
    LMs            : list of tuples (L,M) in the same order as in the 1st axis of `Amo`.
    """
    # wave number for kinetic energy E=1/2 k^2
    k = np.sqrt(2 * energy)
    # wavelength
    wavelength = 2.0 * np.pi / k

    # radial grid
    # sample points and weights for Gauss-Legendre quadrature on the interval [-1,1]
    leggauss_pts, leggauss_weights = legendre.leggauss(npts_r)
    # For Gaussian quadrature the integral [-1,1] has to be changed into [rmax,rmax+2pi/k].
    # new endpoints of interval
    a = rmax
    b = rmax + wavelength
    # transformed sampling points and weights
    r = 0.5 * (b - a) * leggauss_pts + 0.5 * (a + b)
    dr = 0.5 * (b - a) * leggauss_weights

    # Lebedev grid for spherical wave expansion
    th, ph, weights_angular = get_lebedev_grid(lebedev_order)
    # The lebedev quadrature rule is
    #         /
    #  I[f] = | dOmega f(Omega) = 4 pi sum  weights_angular[i] * f(th[i],ph[i])
    #         /                           i
    # For convencience we multiply the factor of 4*pi into the weights
    weights_angular *= 4 * np.pi

    # evaluate spherical harmonics for L=0,1,2, M=-L,..,L on angular Lebedev grid
    Lmax = 2
    Ys, LMs = spherical_harmonics_vec(th, ph, Lmax)
    Ys = np.array(Ys)
    # cartesian coordinates of radial and angular grids
    x = outerN(r, np.sin(th) * np.cos(ph))
    y = outerN(r, np.sin(th) * np.sin(ph))
    z = outerN(r, np.cos(th))
    # make sure r**2*dr has the same shape as x,y and z
    r2dr = np.repeat(np.reshape(r**2 * dr, (len(r), 1)), len(th), axis=1)
    # nc: number of continuum wavefunctions
    nc = len(continuum_orbs)
    # number of angular components (L,M) for Lmax=2
    LMdim = Lmax * (Lmax + 2) + 1
    assert LMdim == len(LMs)
    # set up array for expansion coefficients
    Amo = np.zeros((LMdim, nc, nc), dtype=complex)

    for a in range(0, nc):  # loop over continuum orbitals
        print " %d of %d done" % (a, nc)
        wfn_a = continuum_orbs[a].amp(x, y, z)
        for b in range(0, nc):  # loop over continuum orbitals
            wfn_b = continuum_orbs[b].amp(x, y, z)
            #                /rmax+2pi/k  2      *
            # PAD   (th,ph)= |           r dr wfn (r,th,ph) wfn (r,th,ph)
            #    a,b         /rmax               a             b
            PAD_ab = np.sum(r2dr * wfn_a.conjugate() * wfn_b, axis=0)
            """
            ### DEBUG
            # plot PAD for orbitals a and b
            import matplotlib.pyplot as plt
            from scipy.interpolate import griddata
            th_interp, ph_interp = np.mgrid[0.0:np.pi:100j, 0.0:2*np.pi:100j]
            PAD_ab_interp = griddata((th,ph), PAD_ab, (th_interp, ph_interp))
            plt.cla()
            plt.title(r"PAD$_{%d,%d}$" % (a,b))
            plt.xlabel(r"$\theta$ / $\pi$")
            plt.ylabel(r"$\phi$ / $\pi$")
            plt.imshow(PAD_ab_interp.T, extend=(0.0, 1.0, 0.0, 2.0))
            plt.colorbar()
            plt.show()
            ###
            """
            Amo[:, a, b] = np.dot(Ys.conjugate(), weights_angular * PAD_ab)

    print "normalize Amo's"
    # Continuum wavefunctions are not normalized, we normalize them in such a way
    # the integrating the product of two continuum orbitals
    #
    #   /rmax+2pi/k  2   /            *
    #   |           r dr | dOmega  wfn (r,Omega) wfn (r,Omega)   =  delta
    #   /rmax            /            a             b                    a,b
    #
    # over one wavelength at a very large r gives exactly 1, if a=b, and 0 otherwise.
    # The A's are the expansion coefficients of
    #
    #                                M=+L   a,b
    #  PAD_{a,b}(Omega) = sum     sum      A    Y (Omega)
    #                        L=0     M=-L   L,M  L,M
    #
    #  Therefore we need to normalize the A's such that
    #
    #  /                            a,b               a,b             !
    #  | dOmega PAD_{a,b}(Omega) = A    4*pi Y     = A    sqrt(4 pi)  = 1
    #  /                            0,0       0,0     0,0
    #                   a,b
    #  which implies   A    = 1/sqrt(4*pi) delta
    #                   0,0                     a,b
    nrm = np.zeros(nc, dtype=complex)
    for a in range(0, nc):
        nrm[a] = np.sqrt(Amo[0, a, a])
    # normalize Amo's
    for a in range(0, nc):
        for b in range(0, nc):
            Amo[:, a, b] /= nrm[a] * nrm[b]
    Amo /= np.sqrt(4.0 * np.pi)

    print "       PAD_ab(th,ph) - real part          "
    print "=========================================="
    print "expansion into spherical harmonics        "
    print "                        a,b               "
    print "PAD   (th,ph) = sum    A     Y   (th,hp)  "
    print "   a,b             L,M  L,M   L,M         "
    print "                                          "
    for iLM, (L, M) in enumerate(LMs):
        print " L=%d M=%+d        " % (L, M)
        print "   a,b   "
        print "  A      "
        print "   %d,%+d" % (L, M)
        row_labels = ["a %2.1d" % a for a in range(0, nc)]
        col_labels = ["b %2.1d" % b for b in range(0, nc)]
        txt = annotated_matrix(Amo[iLM, :, :].real, row_labels, col_labels)
        print txt
    """
    ### DEBUG
    Amo_test = np.zeros((LMdim,nc,nc), dtype=complex)
    print "plot normalized wavefunctions"
    for a in range(0, nc):  # loop over continuum orbitals
        print " %d of %d done" % (a, nc)
        wfn_a = continuum_orbs[a].amp(x,y,z) / nrm[a]
        for b in range(0, nc):  # loop over continuum orbitals
            wfn_b = continuum_orbs[b].amp(x,y,z) / nrm[b]
            ### DEBUG
            import matplotlib.pyplot as plt
            plt.cla()
            plt.xlabel("r / bohr")
            plt.plot(r, wfn_a[:,0], label=r"$\phi_{%d}(r;\theta=\theta_0)$" % a)
            plt.plot(r, wfn_b[:,0], label=r"$\phi_{%d}(r;\theta=\theta_0)$" % b)
            plt.plot(r, wfn_a[:,len(th)/2], label=r"$\phi_{%d}(r;\theta=\theta_1)$" % a)
            plt.plot(r, wfn_b[:,len(th)/2], label=r"$\phi_{%d}(r;\theta=\theta_1)$" % b)
            plt.legend()
            plt.show()
            ###

            #                /rmax+2pi/k  2      *
            # PAD   (th,ph)= |           r dr wfn (r,th,ph) wfn (r,th,ph)
            #    a,b         /rmax               a             b
            PAD_ab = np.sum(r2dr * wfn_a.conjugate() * wfn_b, axis=0)
            Amo_test[:,a,b] =  np.dot(Ys.conjugate(), weights_angular * PAD_ab)
            print "a= %d b= %d" % (a,b)
            print "A2m (L=0,M=0)= %s" % Amo_test[0,a,b]
            print "A2m (numerical) = %s" % Amo_test[-5:,a,b]
    ### 
    """

    return Amo, LMs
Ejemplo n.º 11
0
    def getScalarCouplings(self, threshold=0.01):
        """
        Parameters:
        ===========
        threshold: smaller excitation coefficients are neglected in the calculation
           of overlaps

        Returns:
        ========
        coupl: antisymmetric matrix with scalar non-adiabatic coupling
          coupl[A,B] ~ <Psi_A|d/dt Psi_B>*dt

        The coupling matrix has to be divided by the nuclear time step dt

        It is important that this function is called
        exactly once after calling 'getEnergiesAndGradient'
        """
        atomlist2 = self.atomlist
        orbs2 = self.tddftb.dftb2.orbs
        C2 = self.tddftb.Cij[:self.Nst -
                             1, :, :]  # only save the states of interest
        # off-diagonal elements of electronic hamiltonian
        SC = ScalarCoupling(self.tddftb)
        # retrieve last calculation
        if self.last_calculation == None:
            # This is the first calculation, so use the same data
            atomlist1, orbs1, C1 = atomlist2, orbs2, C2
        else:
            atomlist1, orbs1, C1 = self.last_calculation
        Sci = SC.ci_overlap(atomlist1,
                            orbs1,
                            C1,
                            atomlist2,
                            orbs2,
                            C2,
                            self.Nst,
                            threshold=threshold)
        # align phases
        # The eigenvalue solver produces vectors with arbitrary global phases
        # (+1 or -1). The orbitals of the ground state can also change their signs.
        # Eigen states from neighbouring geometries should change continuously.
        signs = np.sign(np.diag(Sci))
        self.P = np.diag(signs)
        # align C2 with C1
        C2 = np.tensordot(self.P[1:, :][:, 1:], C2, axes=(0, 0))
        # align overlap matrix
        Sci = np.dot(Sci, self.P)
        # The relative signs for the overlap between the ground and excited states at different geometries
        # cannot be deduced from the diagonal elements of Sci. The phases are chosen such that the coupling
        # between S0 and S1-SN changes smoothly for most of the states.
        if hasattr(self, "last_coupl"):
            s = np.sign(self.last_coupl[0, 1:] / Sci[0, 1:])
            w = np.abs(Sci[0, 1:] - self.last_coupl[0, 1:])
            mean_sign = np.sign(np.sum(w * s) / np.sum(w))
            sign0I = mean_sign
            for I in range(1, self.Nst):
                Sci[0, I] *= sign0I
                Sci[I, 0] *= sign0I
        #
        state_labels = [("S%d" % I) for I in range(0, self.Nst)]
        if self.tddftb.dftb2.verbose > 0:
            print "Overlap <Psi(t)|Psi(t+dt)>"
            print "=========================="
            print utils.annotated_matrix(Sci, state_labels, state_labels)
        # overlap between wavefunctions at different time steps
        olap = np.copy(Sci)
        coupl = Sci
        # coupl[A,B] = <Psi_A(t)|Psi_B(t+dt)> - delta_AB
        #            ~ <Psi_A(t)|d/dR Psi_B(t)>*dR/dt dt
        # TEST
        # The scalar coupling matrix should be more or less anti-symmetric
        # provided the time-step is small enough
        # set diagonal elements of coupl to zero
        coupl[np.diag_indices_from(coupl)] = 0.0
        err = np.sum(abs(coupl + coupl.transpose()))
        if err > 1.0e-1:
            print "WARNING: Scalar coupling matrix is not antisymmetric, error = %s" % err
        #
        # Because of the finite time-step it will not be completely antisymmetric,
        # so antisymmetrize it
        coupl = 0.5 * (coupl - coupl.transpose())
        # save coupling for establishing relative phases
        self.last_coupl = coupl
        state_labels = [("S%d" % I) for I in range(0, self.Nst)]
        if self.tddftb.dftb2.verbose > 0:
            print "Scalar Coupling"
            print "==============="
            print utils.annotated_matrix(coupl, state_labels, state_labels)

        # save last calculation
        self.last_calculation = (atomlist2, orbs2, C2)
        return coupl, olap
def atomic_ion_averaged_pad_scan(energy_range,
                                 data_file,
                                 npts_r=60,
                                 rmax=300.0,
                                 lebedev_order=23,
                                 radial_grid_factor=3,
                                 units="eV-Mb",
                                 tdip_threshold=1.0e-5):
    """
    compute the photoelectron angular distribution for an ensemble of istropically
    oriented atomic ions.

    Parameters
    ----------
    energy_range    : numpy array with photoelectron kinetic energies (PKE)
                      for which the PAD should be calculated
    data_file       : path to file, a table with PKE, SIGMA and BETA is written

    Optional
    --------
    npts_r          : number of radial grid points for integration on interval [rmax,rmax+2pi/k]
    rmax            : large radius at which the continuum orbitals can be matched 
                      with the asymptotic solution
    lebedev_order   : order of Lebedev grid for angular integrals
    radial_grid_factor 
                    : factor by which the number of grid points is increased
                      for integration on the interval [0,+inf]
    units           : units for energies and photoionization cross section in output, 'eV-Mb' (eV and Megabarn) or 'a.u.'
    tdip_threshold  : continuum orbitals |f> are neglected if their transition dipole moments mu = |<i|r|f>| to the
                      initial orbital |i> are below this threshold.
    """
    print ""
    print "*******************************************"
    print "*  PHOTOELECTRON ANGULAR DISTRIBUTIONS    *"
    print "*******************************************"
    print ""
    Z = 1
    atomlist = [(Z, (0.0, 0.0, 0.0))]

    # determine the radius of the sphere where the angular distribution is calculated. It should be
    # much larger than the extent of the molecule
    (xmin, xmax), (ymin, ymax), (zmin, zmax) = Cube.get_bbox(atomlist,
                                                             dbuff=0.0)
    dx, dy, dz = xmax - xmin, ymax - ymin, zmax - zmin
    # increase rmax by the size of the molecule
    rmax += max([dx, dy, dz])
    Npts = max(int(rmax), 1) * 50
    print "Radius of sphere around molecule, rmax = %s bohr" % rmax
    print "Points on radial grid, Npts = %d" % Npts

    # load bound pseudoatoms
    basis = AtomicBasisSet(atomlist, confined=False)
    bound_orbital = basis.bfs[0]

    # compute PADs for all energies
    pad_data = []
    print "  SCAN"

    print "  Writing table with PAD to %s" % data_file
    # table headers
    header = ""
    header += "# npts_r: %s  rmax: %s" % (npts_r, rmax) + '\n'
    header += "# lebedev_order: %s  radial_grid_factor: %s" % (
        lebedev_order, radial_grid_factor) + '\n'
    if units == "eV-Mb":
        header += "# PKE/eV     SIGMA/Mb       BETA2" + '\n'
    else:
        header += "# PKE/Eh     SIGMA/bohr^2   BETA2" + '\n'

    # save table
    access_mode = MPI.MODE_WRONLY | MPI.MODE_CREATE
    fh = MPI.File.Open(comm, data_file, access_mode)
    if rank == 0:
        # only process 0 write the header
        fh.Write_ordered(header)
    else:
        fh.Write_ordered('')

    for i, energy in enumerate(energy_range):
        if i % size == rank:
            print "    PKE = %6.6f Hartree  (%4.4f eV)" % (
                energy, energy * AtomicData.hartree_to_eV)

            # continuum orbitals at a given energy
            continuum_orbitals = []
            # quantum numbers of continuum orbitals (n,l,m)
            quantum_numbers = []
            phase_shifts = []

            print "compute atomic continuum orbitals ..."
            valorbs, radial_val, phase_shifts_val = load_pseudo_atoms_scattering(
                atomlist, energy, rmin=0.0, rmax=2 * rmax, Npts=100000, lmax=3)

            pos = np.array([0.0, 0.0, 0.0])
            for indx, (n, l, m) in enumerate(valorbs[Z]):
                continuum_orbital = AtomicBasisFunction(
                    Z, pos, n, l, m, radial_val[Z][indx], 0)
                continuum_orbitals.append(continuum_orbital)
                quantum_numbers.append((n, l, m))
                phase_shifts.append(phase_shifts_val[Z][indx])

            # transition dipoles between bound and free orbitals
            dipoles = transition_dipole_integrals(
                atomlist, [bound_orbital],
                continuum_orbitals,
                radial_grid_factor=radial_grid_factor,
                lebedev_order=lebedev_order)
            # Continuum orbitals with vanishing transition dipole moments to the initial orbital
            # do not contribute to the PAD. Filter out those continuum orbitals |f> for which
            # mu^2 = |<i|r|f>|^2  <  threshold
            mu = np.sqrt(np.sum(dipoles[0, :, :]**2, axis=-1))
            dipoles_important = dipoles[0, mu > tdip_threshold, :]
            continuum_orbitals_important = [
                continuum_orbitals[i]
                for i in range(0, len(continuum_orbitals))
                if mu[i] > tdip_threshold
            ]
            quantum_numbers_important = [
                quantum_numbers[i] for i in range(0, len(continuum_orbitals))
                if mu[i] > tdip_threshold
            ]
            phase_shifts_important = [[
                phase_shifts[i]
            ] for i in range(0, len(continuum_orbitals))
                                      if mu[i] > tdip_threshold]

            print " %d of %d continuum orbitals have non-vanishing transition dipoles " \
                % (len(continuum_orbitals_important), len(continuum_orbitals))
            print " to initial orbital (|<i|r|f>| > %e)" % tdip_threshold

            print "  Quantum Numbers"
            print "  ==============="
            row_labels = [
                "orb. %2.1d" % a
                for a in range(0, len(quantum_numbers_important))
            ]
            col_labels = ["N", "L", "M"]
            txt = annotated_matrix(np.array(quantum_numbers_important),
                                   row_labels,
                                   col_labels,
                                   format="%s")
            print txt

            print "  Phase Shifts (in units of pi)"
            print "  ============================="
            row_labels = [
                "orb. %2.1d" % a for a in range(0, len(phase_shifts_important))
            ]
            col_labels = ["Delta"]
            txt = annotated_matrix(np.array(phase_shifts_important) / np.pi,
                                   row_labels,
                                   col_labels,
                                   format="  %8.6f  ")
            print txt

            print "  Transition Dipoles"
            print "  =================="
            print "  threshold = %e" % tdip_threshold
            row_labels = [
                "orb. %2.1d" % a
                for a in range(0, len(quantum_numbers_important))
            ]
            col_labels = ["X", "Y", "Z"]
            txt = annotated_matrix(dipoles_important, row_labels, col_labels)
            print txt

            #
            Cs, LMs = asymptotic_Ylm(continuum_orbitals_important,
                                     energy,
                                     rmax=rmax,
                                     npts_r=npts_r,
                                     lebedev_order=lebedev_order)

            # expand product of continuum orbitals into spherical harmonics of order L=0,2
            Amo, LMs = angular_product_distribution(
                continuum_orbitals_important,
                energy,
                rmax=rmax,
                npts_r=npts_r,
                lebedev_order=lebedev_order)

            # compute PAD for ionization from orbital 0 (the bound orbital)
            sigma, beta = photoangular_distribution(dipoles_important, Amo,
                                                    LMs, energy)

            if units == "eV-Mb":
                energy *= AtomicData.hartree_to_eV
                # convert cross section sigma from bohr^2 to Mb
                sigma *= AtomicData.bohr2_to_megabarn

            pad_data.append([energy, sigma, beta])
            # save row with PAD for this energy to table
            row = "%10.6f   %10.6e  %+10.6e" % tuple(pad_data[-1]) + '\n'
            fh.Write_ordered(row)

    fh.Close()

    print "  Photoelectron Angular Distribution"
    print "  =================================="
    print "  units: %s" % units
    row_labels = [" " for en in energy_range]
    col_labels = ["energy", "sigma", "beta2"]
    txt = annotated_matrix(np.array(pad_data).real, row_labels, col_labels)
    print txt

    print "FINISHED"
def pairwise_decomposition_analytical(atomlist, forcelist):
    Nat = len(atomlist)
    active_atoms = [i for i in range(0, Nat)]
    print "Nat = %s" % Nat
    nx = zeros((Nat, Nat))
    ny = zeros((Nat, Nat))
    nz = zeros((Nat, Nat))
    Fx = zeros(Nat)
    Fy = zeros(Nat)
    Fz = zeros(Nat)
    distances = zeros((Nat, Nat))
    for l, active_l in enumerate(active_atoms):
        (Zl, posl) = atomlist[active_l]
        # unit vectors along bond lengths
        for i, active_i in enumerate(active_atoms):
            (Zi, posi) = atomlist[active_i]
            if i == l:
                continue
            r_li = array(posl) - array(posi)
            nrm = norm(r_li)
            distances[l, i] = nrm
            n_li = r_li / nrm
            nx[l, i] = n_li[0]
            ny[l, i] = n_li[1]
            nz[l, i] = n_li[2]
        # cartesian forces
        Fx[l] = forcelist[l][1][0]
        Fy[l] = forcelist[l][1][1]
        Fz[l] = forcelist[l][1][2]

    dim = (pow(Nat, 2) - Nat) / 2
    M = zeros((dim, dim))
    Msym = [["" for i in range(0, dim)] for j in range(0, dim)]
    visited = zeros((dim, dim), dtype=int)

    R = zeros((dim, dim))
    I = SymmatIndeces(Nat)
    for i in range(0, dim):
        ai, bi = I.i2p(i)
        assert ai < bi
        M[i, i] = 2.0
        Msym[i][i] = "     2     "
        for j in range(i + 1, dim):
            aj, bj = I.i2p(j)
            assert aj < bj
            ridrj = nx[ai,bi] * nx[aj,bj] \
                    + ny[ai,bi] * ny[aj,bj] \
                    + nz[ai,bi] * nz[aj,bj]
            if ai != aj and bi != aj and bi == bj:
                M[i, j] += ridrj
                Msym[i][j] += " r_%d%d*r_%d%d " % (ai + 1, bi + 1, aj + 1,
                                                   bj + 1)
                visited[i, j] += 1
                visited[j, i] += 1
            if ai == aj and ai != bj and bi != bj:
                M[i, j] += ridrj
                Msym[i][j] += " r_%d%d*r_%d%d " % (ai + 1, bi + 1, aj + 1,
                                                   bj + 1)
                visited[i, j] += 1
                visited[j, i] += 1
            if ai == bj and aj != bi and ai != aj:
                M[i, j] -= ridrj
                Msym[i][j] += "-r_%d%d*r_%d%d " % (ai + 1, bi + 1, aj + 1,
                                                   bj + 1)
                visited[i, j] += 1
                visited[j, i] += 1
            if bi == aj and ai != bj and ai != aj:
                M[i, j] -= ridrj
                Msym[i][j] += "-r_%d%d*r_%d%d " % (ai + 1, bi + 1, aj + 1,
                                                   bj + 1)
                visited[i, j] += 1
                visited[j, i] += 1

            M[j, i] = M[i, j]
            Msym[j][i] = Msym[i][j]
    print "Msym"
    for i in range(0, dim):
        for j in range(0, dim):
            if Msym[i][j] == "":
                print "     0     ",
            else:
                print Msym[i][j],
        print ""
    """
    for alpha in range(0, Nat):
        for beta in range(alpha+1, Nat):
            M[symmat_index(alpha,beta,Nat),symmat_index(alpha,beta,Nat)] = 2.0
            visited[symmat_index(alpha,beta,Nat),symmat_index(alpha,beta,Nat)] = 1
            for i in range(0, Nat):
                if i == alpha or i == beta:
                    continue

#                M[symmat_index(alpha,beta,Nat), symmat_index(i,beta, Nat)] \
#                        += nx[alpha,beta] * nx[i,beta] \
#                        + ny[alpha,beta] * ny[i,beta] \
#                        + nz[alpha,beta] * nz[i,beta]
#                M[symmat_index(alpha,beta,Nat), symmat_index(i,alpha, Nat)] \
#                        -= nx[alpha,beta] * nx[i,alpha] \
#                        + ny[alpha,beta] * ny[i,alpha] \
#                        + nz[alpha,beta] * nz[i,alpha]

                M[symmat_index(alpha,beta,Nat), symmat_index(i,beta, Nat)] \
                        = nx[alpha,beta] * nx[i,beta] \
                        + ny[alpha,beta] * ny[i,beta] \
                        + nz[alpha,beta] * nz[i,beta]
                visited[symmat_index(alpha,beta,Nat), symmat_index(i,beta, Nat)] = 1
                M[symmat_index(alpha,beta,Nat), symmat_index(i,alpha, Nat)] \
                        = -nx[alpha,beta] * nx[i,alpha] \
                        - ny[alpha,beta] * ny[i,alpha] \
                        - nz[alpha,beta] * nz[i,alpha]
                visited[symmat_index(alpha,beta,Nat), symmat_index(i,alpha, Nat)] = 1
    """
    print "Which matrix elements were not set?"
    txt = ""
    for i in range(0, dim):
        for j in range(0, dim):
            if visited[i, j] != 0:
                txt += "%d" % visited[i, j]
            else:
                txt += "0"
        txt += "\n"
    print txt
    #
    """
    b = zeros(dim)
    for alpha in range(0, len(atomlist)):
        for beta in range(alpha+1, len(atomlist)):
            b[symmat_index(alpha,beta,Nat)] -= \
                    + (Fx[beta] - Fx[alpha])*nx[alpha,beta] \
                    + (Fy[beta] - Fy[alpha])*ny[alpha,beta] \
                    + (Fz[beta] - Fz[alpha])*nz[alpha,beta]
    """
    b = zeros(dim)
    for i in range(0, dim):
        ai, bi = I.i2p(i)
        b[i] = -(                       \
            (Fx[bi] - Fx[ai])*nx[ai,bi] \
           +(Fy[bi] - Fy[ai])*ny[ai,bi] \
           +(Fz[bi] - Fz[ai])*nz[ai,bi])

    print "M = \n"
    labels = ["r_%d%d" % I.i2p(i) for i in range(0, dim)]
    print utils.annotated_matrix(M, labels, labels)
    print "det(M) = %s" % det(M)
    print "b = \n"
    print b

    # check that M is symmetric
    for i in range(0, dim):
        for j in range(0, dim):
            assert M[i, j] == M[j, i]
    #
    """
    if det(M) != 0.0:
        print "solve"
        pair_force_opt = lapack.solve(M,b)
    else:
        print "lstsq"
        pair_force_opt = lstsq(M,b)[0]
    """
    pair_force_opt, residuals, rank, singvals = lstsq(M, b)
    print "residuals"
    print residuals
    print "rank"
    print rank
    print "singular values"
    print singvals

    print "x = \n"
    print pair_force_opt

    bond_lengths = {}
    pair_forces = {}
    for i, active_i in enumerate(active_atoms):
        (Zi, posi) = atomlist[active_i]
        for j, active_j in enumerate(active_atoms):
            (Zj, posj) = atomlist[active_j]
            if active_i < active_j:
                bond_lengths[(active_i,
                              active_j)] = norm(array(posi) - array(posj))
                pair_forces[(active_i,
                             active_j)] = pair_force_opt[symmat_index(
                                 i, j, Nat)]

    import string
    print "             Pairwise Forces:"
    print "             ================"
    print "        atom i - atom j       bond length [bohr]   bond length [AA]      force F_ij [hartree/bohr]             force F_ij [eV/AA]"
    for active_i, active_j in pair_forces:
        print "           %s-  %s   %s %s %s %s" % \
                (string.ljust("%s%d" % (atom_names[atomlist[active_i][0]-1], active_i+1), 4), \
                 string.ljust("%s%d" % (atom_names[atomlist[active_j][0]-1], active_j+1), 4), \
                 string.rjust("%.7f bohr" % bond_lengths[(active_i,active_j)], 20), \
                 string.rjust("%.7f AA" % (bond_lengths[(active_i,active_j)]*bohr_to_angs), 20), \
                 string.rjust("%.7f hartree/bohr" % pair_forces[(active_i,active_j)], 30), \
                 string.rjust("%.7f eV/AA" % (pair_forces[(active_i,active_j)]*hartree_to_eV/bohr_to_angs), 30))

    def error(pair_force):
        """
        Not every potential can be decomposed into a sum a pair potentials.
        This function determines the error incurred by making the decomposition.
        """
        dVpair = zeros((Nat, Nat))
        for i in range(0, Nat):
            for j in range(i, Nat):
                if i == j:
                    # dVpair[i,i] = 0
                    continue
                # F_ij = - d/dr V_ij(r)
                dVpair[i, j] = -pair_force[symmat_index(i, j, Nat)]
                dVpair[j, i] = dVpair[i, j]
        Sx = 0.0
        Sy = 0.0
        Sz = 0.0
        for l in range(0, Nat):
            dSx = Fx[l]
            dSy = Fy[l]
            dSz = Fz[l]
            for i in range(0, Nat):
                if i == l:
                    continue
                dSx += dVpair[l, i] * nx[l, i]
                dSy += dVpair[l, i] * ny[l, i]
                dSz += dVpair[l, i] * nz[l, i]
            Sx += abs(dSx)
            Sy += abs(dSy)
            Sz += abs(dSz)
        S = Sx + Sy + Sz
        return S

    e = error(pair_force_opt)
    error = e / float(Nat)
    print "ERROR PER ATOM = %s" % error
    return bond_lengths, pair_forces, error