Esempio n. 1
0
def ks_objective_function(density_in, params, precond=None):
    """
    The Kohn-Sham objective function, or residual map, R[rho] = F[rho] - rho == 0?
    Root find with repeated calls to this map to get solution to the KS equations.
    """

    # Construct Hamiltonian
    hamiltonian = construct_hamiltonian(params, density_in)

    # Solve H psi = E psi
    eigenvalues, eigenvectors = np.linalg.eigh(hamiltonian)
    eigenvectors = normalise_function(params, eigenvectors[:, 0:])

    # Extract lowest lying num_particles eigenfunctions and normalise
    wavefunctions_ks = eigenvectors[:, 0:params.num_particles]
    wavefunctions_ks[:, 0:params.num_particles] = normalise_function(
        params, wavefunctions_ks[:, 0:params.num_particles])

    # Calculate the output density
    density_out = calculate_density_ks(params, wavefunctions_ks)

    # Residual map
    residual = density_out - density_in

    # Print error
    print(np.sum(abs(residual)))
    if precond is None:
        return residual
    else:
        return np.dot(np.linalg.inv(precond), residual)
Esempio n. 2
0
def ks_objective_function(density_in, params):
    """
    The KS objective function or `oracle': queried to take numerical derivatives (Jacobian) in
    Newton's method.
    """

    # Construct Hamiltonian
    hamiltonian = construct_hamiltonian(params, density_in)

    # Solve H psi = E psi
    eigenvalues, eigenvectors = np.linalg.eigh(hamiltonian)
    eigenvectors = normalise_function(params, eigenvectors[:, 0:])

    # Extract lowest lying num_particles eigenfunctions and normalise
    wavefunctions_ks = eigenvectors[:, 0:params.num_particles]
    wavefunctions_ks[:, 0:params.num_particles] = normalise_function(
        params, wavefunctions_ks[:, 0:params.num_particles])

    # Calculate the output density
    density_out = calculate_density_ks(params, wavefunctions_ks)

    # Residual map
    residual = density_out - density_in

    return residual
Esempio n. 3
0
def evolution_objective_function(v_ks,params,wavefunctions_ks,density_reference,objective_type,evolution_type):
    r"""
    The objective function for root finding and optimisation algorithms, defined with some
    method of evolving psi(t) --> psi(t+dt).
    """

    # Evolve the wavefunctions according to the given scheme
    if (evolution_type == 'CN'):
        wavefunctions_ks = crank_nicolson_step(params,v_ks,wavefunctions_ks)
    elif (evolution_type == 'expm'):
        wavefunctions_ks = expm_step(params,v_ks,wavefunctions_ks)
    else:
        raise RuntimeError('Invalid time evolution method specified')

    wavefunctions_ks[:,0:params.num_electrons] = normalise_function(params,wavefunctions_ks[:,0:params.num_electrons])

    # Compute evolved density from the evolved wavefunctions
    density_ks = calculate_density_ks(params, wavefunctions_ks[:,:])

    # Error in the KS density away from the reference density
    error = norm(params,density_ks[:] - density_reference[:],'C2')

    # Return a particular output that defines the objective to be minimised
    if (objective_type == 'root'):
        return density_ks - density_reference
    elif (objective_type == 'opt'):
        return error
    else:
        raise RuntimeError('Not a valid type for the objective function output')
Esempio n. 4
0
def minimise_energy_hf(params):
    r"""
    Compute ground state Hartree-Fock solution
    """

    # Generate initial guess density (sum weighted Gaussians)
    dmatrix_in = initial_guess_dmatrix(params)
    density_in = np.diagonal(dmatrix_in)

    # Construct the independent part of the hamiltonian, i.e. KE + v_external
    hamiltonian_independent = construct_hamiltonian_independent(params)

    # SCF loop
    i, error = 0, 1
    while error > params.tol_hf:

        # Update hamiltonian with the orbital-dependent terms
        hamiltonian = update_hamiltonian(params, hamiltonian_independent,
                                         density_in, dmatrix_in)

        # Solve H psi = E psi
        eigenvalues, eigenvectors = linalg.eigh(hamiltonian)

        # Extract lowest lying num_particles eigenfunctions and normalise
        wavefunctions_ks = eigenvectors[:, 0:params.num_particles]
        wavefunctions_ks[:, 0:params.num_particles] = normalise_function(
            params, wavefunctions_ks[:, 0:params.num_particles])

        # Calculate the output density
        dmatrix_out = calculate_dmatrix(params, wavefunctions_ks)
        density_out = np.diagonal(dmatrix_out)

        # Calculate total energy
        total_energy = evaluate_energy_functional(params, wavefunctions_ks,
                                                  density_out, dmatrix_out)

        # L1 error between input and output densities
        error = np.linalg.norm(dmatrix_in - dmatrix_out)
        print('SCF error = {0} at iteration {1} with energy {2}'.format(
            error, i, total_energy))

        step_length = ODA(params, dmatrix_in, dmatrix_out)
        dmatrix_in = dmatrix_in - step_length * (dmatrix_in - dmatrix_out)
        density_in = np.diagonal(dmatrix_in)

        i += 1

    return wavefunctions_ks, total_energy, density_out
Esempio n. 5
0
def initial_guess_dmatrix(params):
    r"""
    Initial guess density matrix by running a calculation with just the independent parts of the Hamiltonian
    """

    # Independent Hamiltonian
    hamiltonian = construct_hamiltonian_independent(params)

    # Solve H psi = E psi
    eigenvalues, eigenvectors = linalg.eigh(hamiltonian)

    # Extract lowest lying num_particles eigenfunctions and normalise
    wavefunctions_ks = eigenvectors[:, 0:params.num_particles]
    wavefunctions_ks[:, 0:params.num_particles] = normalise_function(
        params, wavefunctions_ks[:, 0:params.num_particles])

    # Calculate the corresponding output density matrix
    dmatrix = calculate_dmatrix(params, wavefunctions_ks)

    return dmatrix
Esempio n. 6
0
def initial_guess_density_nonint(params):
    r"""
    Initial guess as the solution to the 'non-interacting' problem
    """

    # Independent Hamiltonian
    hamiltonian = construct_hamiltonian_independent(params)

    # Solve H psi = E psi
    eigenvalues, eigenvectors = linalg.eigh(hamiltonian)

    # Extract lowest lying num_particles eigenfunctions and normalise
    wavefunctions_ks = eigenvectors[:, 0:params.num_particles]
    wavefunctions_ks[:, 0:params.num_particles] = normalise_function(
        params, wavefunctions_ks[:, 0:params.num_particles])

    # Calculate the corresponding output density matrix
    density = calculate_density_ks(params, wavefunctions_ks)

    return density
Esempio n. 7
0
def groundstate_objective_function(v_ks,params,wavefunctions_ks,density_reference):
    r"""
    Ground state objective function, the root of which is the Kohn-Sham potential that generates a ground
    state reference density
    """

    # Construct Hamiltonian for a given Kohn-Sham potential
    hamiltonian = construct_H(params, v_ks[:])

    # Find eigenvectors and eigenvalues of this Hamiltonian
    eigenenergies_ks, eigenfunctions_ks = sp.linalg.eigh(hamiltonian)

    # Store the lowest N_electron eigenfunctions as wavefunctions
    wavefunctions_ks[:, 0:params.num_electrons] = eigenfunctions_ks[:, 0:params.num_electrons]

    # Normalise the wavefunctions w.r.t the continous L2 norm
    wavefunctions_ks[:, 0:params.num_electrons] = normalise_function(params,wavefunctions_ks[:, 0:params.num_electrons])

    # Construct KS density
    density_ks = np.sum(np.abs(wavefunctions_ks[:, :])**2, axis=1, dtype=np.float)

    return density_reference[:] - density_ks[:]
Esempio n. 8
0
def solve_TISE(params):
    r"""
    Solves the time-independent Schrodinger equation
    """

    # Deal with single-particle case separately as it is significantly more simple
    if params.num_electrons == 1:

        # Construct single-particle H
        hamiltonian = construct_H_dense(params, basis_type='position')

        # Find spectrum of the single-particle H
        eigenenergy, eigenfunction = sp.linalg.eigh(hamiltonian)
        eigenfunction = eigenfunction.real

        # Norm wavefunction
        wavefunction = normalise_function(params, eigenfunction[:,0])

        # Ground state energy (n.b. undo the potential shift)
        eigenenergy = np.amin(eigenenergy) - params.num_electrons*params.v_ext_shift
        print('Ground state energy: {0}'.format(eigenenergy))

        # Compute density
        density = calculate_density_exact(params, wavefunction)

        return wavefunction, density, eigenenergy

    # Deal with N > 1 case

    # Antisymmetry operator A and A^-1 s.t. psi = A phi, for psi antisymmetric, phi distinct elements of psi
    antisymm_expansion, antisymm_reduction = expansion_and_reduction_matrix(params)

    # Generate the initial guess wavefunction for the iterative diagonaliser
    # as a Slater determinant of single-particle wavefunctions
    wavefunction = initial_guess_wavefunction(params)

    # Reduce to unique components
    wavefunction = antisymm_reduction.dot(wavefunction)

    # Construct the sparse many-body Hamiltonian directly in an antisymmetric basis
    hamiltonian = construct_H_sparse(params, basis_type='position')

    # Perform the transformation U^T H U = H': project out the antisymmetric subspace of H
    hamiltonian = hamiltonian.dot(antisymm_expansion)
    hamiltonian = antisymm_reduction.dot(hamiltonian)

    # Regularisation??
    #hamiltonian += sp.sparse.csr_matrix(100*np.eye(len(sp.sparse.csr_matrix.todense(hamiltonian))))

    # Find g.s. eigenvector and eigenvalue using Lanszcos algorithm
    eigenenergy_gs, eigenfunction_gs = sp.sparse.linalg.eigs(hamiltonian, 1, which='SM', v0=-wavefunction)
    #eigenenergy_gs, eigenfunction_gs = sp.linalg.eigh(sp.sparse.csr_matrix.todense(hamiltonian))

    # Expand to full antisymmetric solution
    wavefunction = antisymm_expansion.dot(eigenfunction_gs[:,0])

    # Normalise the eigenfunction
    wavefunction *= (np.sum(abs(wavefunction[:])**2) * params.dx**2)**-0.5

    # Ground state energy (n.b. undo the potential shift)
    #eigenenergy_gs = np.amin(eigenenergy_gs) - params.num_electrons*params.v_ext_shift - 100

    print('Ground state energy: {0}'.format(eigenenergy_gs))

    # Compute density
    density = calculate_density_exact(params, wavefunction)

    return wavefunction, density, eigenenergy_gs
Esempio n. 9
0
def generate_ks_potential(params,density_reference):
    r"""
    Reverse engineers a reference density to product a Kohn-Sham potential

    :param params: input parameters object
    :param density_reference: ndarray, the reference density, axes [time,space]
    :return: density_ks, v_ks, wavefunctions_ks: ndarray, the optimised density, Kohn-Sham potential and wavefunctions
    """

    # Deal with ground state before time-dependence
    # Init variables
    v_ks = np.zeros((params.Ntime,params.Nspace))
    density_ks = np.zeros((params.Ntime,params.Nspace))
    wavefunctions_ks = np.zeros((params.Ntime,params.Nspace,params.num_electrons), dtype=complex)

    # Initial guess for the Kohn-Sham potential
    v_ks[0,:] = params.v_ext
    v_ks[1:,:] = params.v_ext + params.v_pert

    # Compute the ground-state Kohn-Sham potential
    i, error = 0, 1
    while(error > 5e-10):

        # Construct Hamiltonian for a given Kohn-Sham potential
        hamiltonian = construct_H(params,v_ks[0,:])

        # Find eigenvectors and eigenvalues of this Hamiltonian
        eigenenergies_ks, eigenfunctions_ks = sp.linalg.eigh(hamiltonian)
        eigenfunctions_ks = eigenfunctions_ks.real

        # Store the lowest N_electron eigenfunctions as wavefunctions
        wavefunctions_ks[0,:,0:params.num_electrons] = eigenfunctions_ks[:,0:params.num_electrons]

        # Normalise the wavefunctions w.r.t the continous L2 norm
        wavefunctions_ks[0,:,0:params.num_electrons] = normalise_function(params,
                                                                          wavefunctions_ks[0,:,0:params.num_electrons])

        # Construct KS density
        density_ks[0,:] = calculate_density_ks(params, wavefunctions_ks[0,:,:])

        # Error in the KS density away from the reference density
        error = norm(params,density_ks[0,:] - density_reference[0,:],'MAE')

        # Update the KS potential with a steepest descent scheme
        v_ks[0,:] -= 0.01*(density_reference[0,:] - density_ks[0,:]) / density_reference[0,:]
        #v_ks[0,:] -= density_reference[0,:]**0.05 - density_ks[0,:]**0.05

        print('Error = {0} after {1} iterations'.format(error,i), end='\r')

        i += 1

    print('Final error in the ground state KS density is {0} after {1} iterations'.format(error,i))
    print(' ')


    # Compute the ground-state potential using a scipy optimiser
    opt_info = root(groundstate_objective_function, v_ks[0, :], args=(params, wavefunctions_ks[0,:,:], density_reference[0,:],
                                                                      ), method='hybr', tol=1e-16)

    # Output v_ks
    v_ks[0,:] = opt_info.x

    # Compute the corresponding wavefunctions, density, and error
    hamiltonian = construct_H(params, v_ks[0, :])
    eigenenergies_ks, eigenfunctions_ks = sp.linalg.eigh(hamiltonian)
    wavefunctions_ks[0,:,0:params.num_electrons] = normalise_function(params,eigenfunctions_ks[:,0:params.num_electrons])
    density_ks[0, :] = calculate_density_ks(params, wavefunctions_ks[0,:,:])
    error = norm(params, density_ks[0, :] - density_reference[0, :], 'MAE')
    print('Final root finder error = {0} after {1} function evaluations. Status: {2}'.format(error,opt_info.nfev,opt_info.success))
    print(' ')

    # Now optimise the time-dependent KS potential
    for i in range(1,params.Ntime):

        # Find the v_ks that minimises the specified objective function
        opt_info = root(evolution_objective_function,v_ks[i,:],args=(params,wavefunctions_ks[i-1,:,:],density_reference[i,:],
                                                                     'root', 'expm'), method='hybr', tol=1e-16)

        # Final (optimal) Kohn-Sham potential at time step i
        v_ks[i,:] = opt_info.x

        # Compute the evolved wavefunctions given the optimal Kohn-Sham potential
        if params.time_step_method == 'CN':
            wavefunctions_ks[i,:,:] = crank_nicolson_step(params,v_ks[i,:],wavefunctions_ks[i-1,:,:])
        elif params.time_step_method == 'expm':
            wavefunctions_ks[i,:,:] = expm_step(params,v_ks[i,:],wavefunctions_ks[i-1,:,:])

        # Final Kohn-Sham density
        density_ks[i,:] = calculate_density_ks(params, wavefunctions_ks[i,:,:])

        # Final error in the Kohn-Sham density away from the reference density
        error = norm(params, density_ks[i,:] - density_reference[i,:], 'MAE')

        print('Final error in KS density is {0} at time {1} after {2} iterations'.format(error,
                                                                                  round(params.time_grid[i],3),
                                                                                  opt_info.nfev), end='\r')

    print(' ')
    print(' ')

    return density_ks, v_ks, wavefunctions_ks
Esempio n. 10
0
def minimise_energy_dft(params):
    """
    Minimises the Kohn-Sham energy functional by solving the Kohn-Sham equations H[rho] psi = E psi.
    Returns density, wavefunction, and ground state energy
    """

    # Array that will store SCF iterative densities and residuals
    history_of_densities_in = np.zeros((params.history_length, params.Nspace))
    history_of_densities_out = np.zeros((params.history_length, params.Nspace))
    history_of_residuals = np.zeros((params.history_length, params.Nspace))
    density_differences = np.zeros((params.history_length, params.Nspace))
    residual_differences = np.zeros((params.history_length, params.Nspace))

    # Generate initial guess density (sum weighted Gaussians)
    density_in = initial_guess_density_nonint(params)

    # Construct the independent part of the hamiltonian, i.e. KE + v_external
    hamiltonian_independent = construct_hamiltonian_independent(params)

    # Optionally use scipy's rootfinder
    #opt_info = sp.optimize.root(ks_objective_function, density_in, args=(params, precond), method='anderson', tol=1e-13)

    # SCF loop
    i, error = 0, 1
    while error > params.tol_ks:

        # Iteration number modulus history length
        i_mod = i % params.history_length
        i_mod_prev = (i - 1) % params.history_length

        hamiltonian = update_hamiltonian(params, hamiltonian_independent,
                                         density_in)

        # Solve H psi = E psi
        eigenvalues, eigenvectors = linalg.eigh(hamiltonian)
        eigenvectors = normalise_function(params, eigenvectors[:, 0:])

        # Extract lowest lying num_particles eigenfunctions and normalise
        wavefunctions_ks = eigenvectors[:, 0:params.num_particles]
        wavefunctions_ks[:, 0:params.num_particles] = normalise_function(
            params, wavefunctions_ks[:, 0:params.num_particles])

        # Calculate the output density
        density_out = calculate_density_ks(params, wavefunctions_ks)

        # Calculate total energy
        total_energy = evaluate_energy_functional(params, wavefunctions_ks,
                                                  density_out)

        # L1 error between input and output densities
        error = np.sum(abs(density_in - density_out) * params.dx)
        print('SCF error = {0} at iteration {1} with energy {2}'.format(
            error, i, total_energy))

        # Store densities/residuals within the iterative history data
        history_of_densities_in[i_mod, :] = density_in
        history_of_densities_out[i_mod, :] = density_out
        history_of_residuals[i_mod, :] = density_out - density_in

        if i == 0:
            # Damped linear step for the first iteration
            density_in = density_in - params.step_length * (density_in -
                                                            density_out)

        elif i > 0:
            # Store more iterative history data...
            density_differences[i_mod_prev] = history_of_densities_in[
                i_mod] - history_of_densities_in[i_mod_prev]
            residual_differences[i_mod_prev] = history_of_residuals[
                i_mod] - history_of_residuals[i_mod_prev]

            # Perform Pulay step using the iterative history data
            #density_in = pulay_mixing(params, density_differences, residual_differences,
            #                        history_of_residuals[i_mod], history_of_densities_in[i_mod], i)

            density_in = newton_mixing(params,
                                       history_of_densities_in[i_mod],
                                       history_of_residuals[i_mod],
                                       eigenvectors,
                                       eigenvalues,
                                       method='adler-wiser')

        i += 1

    # Plot linear response functions of the converged system
    eigenvalues, eigenvectors = linalg.eigh(hamiltonian)
    eigenvectors = normalise_function(params, eigenvectors[:, 0:])
    susceptibility = calculate_susceptibility(params, eigenvectors,
                                              eigenvalues)

    eigval, eigvec = np.linalg.eigh(susceptibility)
    for i in range(len(eigval)):
        plt.plot(eigvec[:, i])
        plt.show()
        plt.clf()

    dielectric = calculate_dielectric(params, density_out, susceptibility)
    plt.imshow(susceptibility.real, origin='lower')
    plt.colorbar()
    plt.show()

    return wavefunctions_ks, total_energy, density_out
Esempio n. 11
0
def minimise_energy_hf(params):

    # Calculate number of particles
    num_atoms = len(params.species)
    num_particles = 0
    elements = element_charges(params)
    for i in range(0, len(params.species)):
        num_particles += elements[params.species[i]]
    params.num_electrons = num_particles

    # Generate initial guess density (sum weighted Gaussians)
    density_in = initial_guess_density(params)

    # SCF loop
    i, error = 0, 1
    while error > 1e-10:

        # Iteration number modulus history length
        i_mod = i % params.history_length
        i_mod_prev = (i - 1) % params.history_length

        # Construct Hamiltonian
        hamiltonian = construct_ks_hamiltonian(params, density_in)

        # Solve H psi = E psi
        eigenvalues, eigenvectors = sp.linalg.eigh(hamiltonian)

        # Extract lowest lying num_particles eigenfunctions and normalise
        wavefunctions_ks = eigenvectors[:, 0:num_particles]
        wavefunctions_ks[:, 0:num_particles] = normalise_function(
            params, wavefunctions_ks[:, 0:num_particles])

        # Calculate the output density
        density_out = calculate_density_ks(params, wavefunctions_ks)

        # Calculate total energy
        energy = calculate_total_energy(params, eigenvalues[0:num_particles],
                                        density_out)

        # L1 error between input and output densities
        error = np.sum(abs(density_in - density_out) * params.dx_dft)
        print('SCF error = {0} at iteration {1} with energy {2}'.format(
            error, i, energy))

        # Store densities/residuals within the iterative history data
        history_of_densities_in[i_mod, :] = density_in
        history_of_densities_out[i_mod, :] = density_out
        history_of_residuals[i_mod, :] = density_out - density_in

        if i == 0:

            # Damped linear step for the first iteration
            density_in = density_in - params.step_length * (density_in -
                                                            density_out)

        elif i > 0:

            # Store more iterative history data...
            density_differences[i_mod_prev] = history_of_densities_in[
                i_mod] - history_of_densities_in[i_mod_prev]
            residual_differences[i_mod_prev] = history_of_residuals[
                i_mod] - history_of_residuals[i_mod_prev]

            # Perform Pulay step using the iterative history data
            density_in = pulay_mixing_kresse(params, density_differences,
                                             residual_differences,
                                             history_of_residuals[i_mod],
                                             history_of_densities_in[i_mod], i)

        i += 1