From e26ed168a84162b66e623fceef3eae69ecc9a084 Mon Sep 17 00:00:00 2001 From: Bruno Senjean Date: Sat, 7 Oct 2023 16:49:50 +0200 Subject: [PATCH] Added all the QDFT code. --- README.md | 14 + examples/DFT_Hchain.py | 247 ++++++++++ examples/QDFT_Hchain.py | 445 ++++++++++++++++++ examples/QDFT_Hubbard.py | 363 ++++++++++++++ setup.py | 13 + src/QDFT.egg-info/PKG-INFO | 13 + src/QDFT.egg-info/SOURCES.txt | 11 + src/QDFT.egg-info/dependency_links.txt | 1 + src/QDFT.egg-info/top_level.txt | 1 + src/QDFT/__init__.py | 16 + src/QDFT/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 478 bytes src/QDFT/__pycache__/geometry.cpython-39.pyc | Bin 0 -> 1233 bytes .../__pycache__/measurements.cpython-39.pyc | Bin 0 -> 5815 bytes src/QDFT/__pycache__/operators.cpython-39.pyc | Bin 0 -> 2502 bytes src/QDFT/geometry.py | 31 ++ src/QDFT/measurements.py | 199 ++++++++ src/QDFT/operators.py | 77 +++ 17 files changed, 1431 insertions(+) create mode 100755 examples/DFT_Hchain.py create mode 100755 examples/QDFT_Hchain.py create mode 100755 examples/QDFT_Hubbard.py create mode 100644 setup.py create mode 100644 src/QDFT.egg-info/PKG-INFO create mode 100644 src/QDFT.egg-info/SOURCES.txt create mode 100644 src/QDFT.egg-info/dependency_links.txt create mode 100644 src/QDFT.egg-info/top_level.txt create mode 100644 src/QDFT/__init__.py create mode 100644 src/QDFT/__pycache__/__init__.cpython-39.pyc create mode 100644 src/QDFT/__pycache__/geometry.cpython-39.pyc create mode 100644 src/QDFT/__pycache__/measurements.cpython-39.pyc create mode 100644 src/QDFT/__pycache__/operators.cpython-39.pyc create mode 100644 src/QDFT/geometry.py create mode 100644 src/QDFT/measurements.py create mode 100644 src/QDFT/operators.py diff --git a/README.md b/README.md index 5c55f78..ae97d55 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,16 @@ # QDFT Quantum Density Functional Theory: a quantum algorithm to solve the Kohn-Sham self-consistent equations. + +# Installation + +export QDFT_DIR=`pwd` +pip install -e . + +# execution + +cd examples +python3 DFT_Hchain.py +python3 QDFT_Hchain.py +python3 QDFT_Hubbard.py + +For Hubbard, one requires to clone and install git@github.com:bsenjean/SOFT.git diff --git a/examples/DFT_Hchain.py b/examples/DFT_Hchain.py new file mode 100755 index 0000000..6fe9c42 --- /dev/null +++ b/examples/DFT_Hchain.py @@ -0,0 +1,247 @@ +import os,sys +import numpy as np +import math +sys.path.insert(1, os.path.abspath('/usr/local/psi4/lib/')) +import psi4 + +#psi4.set_memory('30 GB') + +working_directory = os.getenv('QDFT_DIR') + "/examples/" + +#===========================================================# +#=============== Initialization by the user ================# +#===========================================================# + +functional = "SVWN" +basis = "sto-3g" +E_conv = 1e-9 +D_conv = 1e-6 +SCF_maxiter = 100 +n_hydrogens = 4 +n_elec = n_hydrogens +n_occ = n_elec//2 +#interdist_list = [0.7,0.8,0.9,1.0,1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9,2.0,2.1,2.2,2.3,2.4,2.5,2.6,2.7,2.8,2.9,3.0] +interdist_list = [4.0] + +#===========================================================# +#=============== End of the initialization =================# +#===========================================================# + +#========================================================================# +#============= START THE ALGORITHM FOR DIFFERENT U VALUES ===============# +#========================================================================# +for R in interdist_list: + + psi4.core.clean() + # Define the geometry + psi4.core.set_output_file(working_directory + "results/H{}_R{}_{}_{}_Psi4.dat".format(n_hydrogens,R,basis,functional),True) + string_geo = "0 1\n" + for d in range(n_hydrogens//2): + string_geo += "H 0. 0. {}\n".format(- (R/2. + d*R)) + string_geo += "H 0. 0. {}\n".format(+ (R/2. + d*R)) + string_geo += "symmetry c1\n" + string_geo += "nocom\n" + string_geo += "noreorient\n" + + psi4.geometry(string_geo) + + psi4.set_options({'basis': basis,'save_jk':True, 'debug':1, 'print':5, 'scf_type':'pk'}) + #psi4.set_options({'basis': basis,'scf_type':'pk'}) + dft_e, dft_wfn = psi4.energy(functional, return_wfn=True) + + # Hcore matrix: + Hcore = dft_wfn.H().clone() + # Nuclear energy: + E_nuc = dft_wfn.get_energies('Nuclear') + # Overlap matrix in the AO basis: + S_AO = dft_wfn.S().np + # Compute the inverse square root of the overlap matrix S + S_eigval, S_eigvec = np.linalg.eigh(S_AO) + S_sqrt_inv = S_eigvec @ np.diag((S_eigval)**(-1./2.)) @ S_eigvec.T + C_transformation = np.linalg.inv(S_sqrt_inv) + + # Construct the SAD Guess for the initial density D_AO + psi4.core.prepare_options_for_module("SCF") + sad_basis_list = psi4.core.BasisSet.build(dft_wfn.molecule(), "ORBITAL", + psi4.core.get_global_option("BASIS"), puream=dft_wfn.basisset().has_puream(), + return_atomlist=True) + sad_fitting_list = psi4.core.BasisSet.build(dft_wfn.molecule(), "DF_BASIS_SAD", + psi4.core.get_option("SCF", "DF_BASIS_SAD"), puream=dft_wfn.basisset().has_puream(), + return_atomlist=True) + + # Use Psi4 SADGuess object to build the SAD Guess + SAD = psi4.core.SADGuess.build_SAD(dft_wfn.basisset(), sad_basis_list) + SAD.set_atomic_fit_bases(sad_fitting_list) + SAD.compute_guess(); + D_AO = SAD.Da().clone() + + # Initialize the potential object + V_xc = dft_wfn.Da().clone() + Vpotential = dft_wfn.V_potential() + + mints = psi4.core.MintsHelper(dft_wfn.basisset()) + I = np.asarray(mints.ao_eri()) + AO_potential = np.asarray(mints.ao_potential()) + AO_kinetic = np.asarray(mints.ao_kinetic()) + + Delta_E = 1e8 + dRMS = 1e8 + Fock_list = [] + DIIS_error = [] + + # Compute the energy of the SAD guess: + # Coulomb potential + J_coulomb = np.einsum('pqrs,rs->pq', I, D_AO.np) + # XC potential + Vpotential.set_D([D_AO]) + Vpotential.compute_V([V_xc]) + # Compute the Hxc energy: + EH = 2*np.einsum('pq,pq->', J_coulomb, D_AO.np) + Exc = Vpotential.quadrature_values()["FUNCTIONAL"] + EHxc = EH + Exc + # Compute the kinetic energy + Ts = 2*np.einsum('pq,pq->', AO_kinetic, D_AO.np) + # Compute the energy contributions from the potentials: + Hxcpot_energy = 2*np.einsum('pq,pq->', (2*J_coulomb + V_xc.np), D_AO.np) + Extpot_energy = 2*np.einsum('pq,pq->', AO_potential, D_AO.np) + Etot = Ts + EHxc + Extpot_energy + E_nuc + print("SAD guess energy: {:24.16f}".format(Etot)) + + # Get the first density matrix after SAD guess: + F_AO = Hcore.np + 2*J_coulomb + V_xc.np + F_OAO = S_sqrt_inv @ F_AO @ S_sqrt_inv + eigvals,eigvecs = np.linalg.eigh(F_OAO) + # Transform back to the AO basis: + C_MO = S_sqrt_inv @ eigvecs + C_occ = S_sqrt_inv @ eigvecs[:,:n_occ] # AO --> OAO = C_transformation = S^{1/2}, OAO --> AO = S^{-1/2} + # Compute the alpha-density matrix + D_AO.np[:] = np.einsum('pi,qi->pq', C_occ, C_occ, optimize=True) + + for SCF_ITER in range(1, SCF_maxiter+1): + + ########################################################################## + # FIRST WAY TO COMPUTE THE ENERGY, WITHOUT DIAGONALIZING THE FOCK MATRIX # + ########################################################################## + + # Update the potential from the new density + J_coulomb = np.einsum('pqrs,rs->pq', I, D_AO.np) + Vpotential.set_D([D_AO]) + Vpotential.compute_V([V_xc]) # Also required to compute the Exc energy + + # Build the Fock matrix (for DIIS) + F_AO = Hcore.np + 2*J_coulomb + V_xc.np + + # DIIS + diis_e = np.einsum('ij,jk,kl->il', F_AO, D_AO.np, S_AO) - np.einsum('ij,jk,kl->il', S_AO, D_AO.np, F_AO) + diis_e = S_sqrt_inv @ diis_e @ S_sqrt_inv + Fock_list.append(F_AO) + DIIS_error.append(diis_e) + dRMS = np.mean(diis_e**2)**0.5 + + # Compute the energy contributions from the potentials, with the density of the same iteration... + Hxcpot_energy = 2*np.einsum('pq,pq->', (2*J_coulomb + V_xc.np), D_AO.np) + Extpot_energy = 2*np.einsum('pq,pq->', AO_potential, D_AO.np) + + # Compute the Hartree energy + EH = 2*np.einsum('pq,pq->', J_coulomb, D_AO.np) + # Compute the XC energy + Exc = Vpotential.quadrature_values()["FUNCTIONAL"] + EHxc = EH + Exc + + # Compute the kinetic energy + Ts = 2*np.einsum('pq,pq->', AO_kinetic, D_AO.np) + + Etot_new = Ts + EHxc + Extpot_energy + E_nuc + + ###################################################################### + # SECOND WAY TO COMPUTE THE ENERGY, BY DIAGONALIZING THE FOCK MATRIX # + ###################################################################### + + F_OAO = S_sqrt_inv @ F_AO @ S_sqrt_inv + eigvals,eigvecs = np.linalg.eigh(F_OAO) + # Transform back to the AO basis: + C_MO = S_sqrt_inv @ eigvecs + C_occ = S_sqrt_inv @ eigvecs[:,:n_occ] # AO --> OAO = C_transformation = S^{1/2}, OAO --> AO = S^{-1/2} + # Compute the alpha-density matrix + D_AO.np[:] = np.einsum('pi,qi->pq', C_occ, C_occ, optimize=True) + # Compute the energy contributions from the old potential with the new density + Hxcpot_energy = 2*np.einsum('pq,pq->', (2*J_coulomb + V_xc.np), D_AO.np) + Extpot_energy = 2*np.einsum('pq,pq->', AO_potential, D_AO.np) + # Compute the Hxc energy: + J_coulomb = np.einsum('pqrs,rs->pq', I, D_AO.np) + EH = 2*np.einsum('pq,pq->', J_coulomb, D_AO.np) + Vpotential.set_D([D_AO]) + Vpotential.compute_V([V_xc]) + Exc = Vpotential.quadrature_values()["FUNCTIONAL"] + EHxc = EH + Exc + Etot_new2 = 2*np.sum(eigvals[:n_occ]) + EHxc - Hxcpot_energy + E_nuc + + # Print results + #print("compare (Ts):",2*np.einsum('pq,pq->', AO_kinetic, D_AO.np),2*np.sum(eigvals[:n_occ])-Hxcpot_energy-Extpot_energy) + print("Energy (hartree) : {:24.16f} {:24.16f}".format(Etot_new,Etot_new2)) + + Delta_E = abs(Etot_new - Etot) + Etot = Etot_new + + if (Delta_E < E_conv) and (dRMS < D_conv): + print("*"*10 + " SUCCESS " + "*"*10) + print("R = ",R) + print("Iteration : {:16d}".format(SCF_ITER)) + print("DFT energy : {:16.8f}".format(Etot)) + print("DFT energy Psi4 : {:16.8f}".format(dft_e)) + break + + if SCF_ITER == SCF_maxiter: + psi4.core.clean() + raise Exception("Maximum number of SCF cycles exceeded.") + + if SCF_ITER >= 2: + + # Limit size of DIIS vector + diis_count = len(Fock_list) + if diis_count > 6: + # Remove oldest vector + del Fock_list[0] + del DIIS_error[0] + diis_count -= 1 + + # Build error matrix B, [Pulay:1980:393], Eqn. 6, LHS + B = np.empty((diis_count + 1, diis_count + 1)) + B[-1, :] = -1 + B[:, -1] = -1 + B[-1, -1] = 0 + for num1, e1 in enumerate(DIIS_error): + for num2, e2 in enumerate(DIIS_error): + if num2 > num1: continue + val = np.einsum('ij,ij->', e1, e2) + B[num1, num2] = val + B[num2, num1] = val + + # normalize + B[:-1, :-1] /= np.abs(B[:-1, :-1]).max() + + # Build residual vector, [Pulay:1980:393], Eqn. 6, RHS + resid = np.zeros(diis_count + 1) + resid[-1] = -1 + + # Solve Pulay equations, [Pulay:1980:393], Eqn. 6 + ci = np.linalg.solve(B, resid) + + # Calculate new fock matrix as linear + # combination of previous fock matrices + F_AO = np.zeros_like(F_AO) + for num, c in enumerate(ci[:-1]): + F_AO += c * Fock_list[num] + + # Build the Fock matrix in the OAO basis: + F_OAO = S_sqrt_inv @ F_AO @ S_sqrt_inv + eigvals,eigvecs = np.linalg.eigh(F_OAO) + #print("varepsilon exacts :",dft_wfn.epsilon_a().np[:]) + #print("varepsilon iteration :",eigvals) + + # Transform back to the AO basis: + C_MO = S_sqrt_inv @ eigvecs + C_occ = S_sqrt_inv @ eigvecs[:,:n_occ] # AO --> OAO = C_transformation = S^{1/2}, OAO --> AO = S^{-1/2} + + # Compute the alpha-density matrix + D_AO.np[:] = np.einsum('pi,qi->pq', C_occ, C_occ, optimize=True) diff --git a/examples/QDFT_Hchain.py b/examples/QDFT_Hchain.py new file mode 100755 index 0000000..8d4278b --- /dev/null +++ b/examples/QDFT_Hchain.py @@ -0,0 +1,445 @@ +import sys, os +import numpy as np +import math +import scipy +import QDFT +# importing Qiskit +from qiskit import Aer, transpile +from qiskit import QuantumCircuit, ClassicalRegister +from qiskit.quantum_info import Statevector +from qiskit.algorithms.optimizers import SPSA +from qiskit.algorithms import NumPyEigensolver +from qiskit.circuit.library import TwoLocal +from qiskit.providers.aer import QasmSimulator, AerSimulator +from qiskit.providers.aer.noise import NoiseModel +from qiskit.providers.fake_provider import FakeVigo +from qiskit.visualization import circuit_drawer +from qiskit.opflow import StateFn + +sys.path.insert(1, os.path.abspath('/usr/local/psi4/lib/')) +import psi4 + +psi4.set_memory('100 GB') + +working_directory = os.getenv('QDFT_DIR') + +#===========================================================# +#=============== Initialization by the user ================# +#===========================================================# + +# Quantum Simulator +nshots = False +simulation = ["noiseless","noisy"][0] # noiseless doesn't mean without sampling noise ! Noisy means a real simulation of a quantum computer. +if simulation == "noiseless": + backend = Aer.get_backend('statevector_simulator') + blocking = False + allowed_increase = None +elif simulation == "noisy": + device_backend = FakeVigo() + device = QasmSimulator.from_backend(device_backend) + noise_model = NoiseModel.from_backend(device) + #import qiskit.providers.aer.noise as noise + ## Error probabilities + #prob_1 = 0.001 # 1-qubit gate + #prob_2 = 0.1 # 2-qubit gate + ## Depolarizing quantum errors + #error_1 = noise.depolarizing_error(prob_1, 1) + #error_2 = noise.depolarizing_error(prob_2, 2) + ## Add errors to noise model + #noise_model = noise.NoiseModel() + #noise_model.add_all_qubit_quantum_error(error_1, ['rz', 'sx', 'x']) + #noise_model.add_all_qubit_quantum_error(error_2, ['cx']) + #backend = QasmSimulator(method='statevector',noise_model=noise_model) + backend = QasmSimulator(method='statevector', noise_model=noise_model) + blocking = True + allowed_increase = 0.1 +else: + sys.exit("Simulation argument '{}' does not exist".format(simulation)) + +n_blocks = 2 # Number of block considered into the Hardware efficient ansatz +rotation_blocks= ['ry','rz',['ry','rz'],['rz','rx','rz']][0] # rotation gates in the ansatz ### I noticed RyRz works best (3 states optimization for instance, but much longer time...) +entanglement = ['full','linear','circular','sca'][1] # the way qubits are entangled in the ansatz +entanglement_blocks = ['cz','cx'][1] # entanglement gate in the ansatz ## I noticed cz works best (3 states optimization) + +# SCF convergence criteria: +E_conv = 1e-5 +D_conv = 1e-4 +SCF_maxiter = 50 +slope_SCF = 5 # set to None is not used. + +# Classical optimizer criteria: +basinhopping = False +niter_hopping = 10 # parameter used for basinhopping +opt_method = ['L-BFGS-B','SLSQP','SPSA'][0] # classical optimizer +opt_maxiter = 1000 # number of iterations of the classical optimizer +ftol = 1e-9 +gtol = 1e-6 +take_min_opt = False # take the minimal energy point of the minimization. It is known not to be a good idea... +resampling = 2 # used for SPSA, see documentation. +slope_SPSA = 25 # set to None is not used. + +# DFT +functional = "SVWN" +basis = "sto-3g" + +# Other options: +run_fci = True + +# System: +n_qubits = 2 +n_orbs = 2**n_qubits # number of hydrogens +n_elec = n_orbs +n_occ = n_elec//2 +#interdist_list = [0.7,0.8,0.9,1.0,1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9,2.0,2.1,2.2,2.3,2.4,2.5,2.6,2.7,2.8,2.9,3.0] +interdist_list = [1.2] + +# SA weights: +weights_choice = ["equi","decreasing"][1] + +#===========================================================# +#=============== End of the initialization =================# +#===========================================================# + +if weights_choice == "equi": + weights = [1./n_occ for i in range(n_occ)] # should be n_occ of them +elif weights_choice == "decreasing": + weights = [(1.+i)/(n_occ*(n_occ+1.)/2) for i in reversed(range(n_occ))] # should be n_occ of them +else: + sys.exit("The choice for the weights-attribution is not defined.") + +#===========================================================# +#================ CIRCUIT IMPLEMENTATION ===================# +#===========================================================# + +# Preparation of orthogonal initial states via different initial circuits (need n_occ of them !) +initial_circuits = [] +for i in range(n_occ): initial_circuits += [QuantumCircuit(n_qubits)] + +for state in range(n_occ): # binarystring representation of the integer + for i in QDFT.list_of_ones(state,n_qubits): + initial_circuits[state].x(i) + +circuits = [TwoLocal(n_qubits,rotation_blocks,entanglement_blocks,entanglement,n_blocks,insert_barriers=True,initial_state=initial_circuits[state]) for state in range(n_occ)] +n_param = circuits[0].num_parameters +param_values= np.zeros(n_param) +print(circuits[0].decompose()) +circuit_drawer(circuits[0].decompose(), scale=None, filename="circuit", style=None, output="latex_source", interactive=True, plot_barriers=True, reverse_bits=False, justify=None, vertical_compression='medium', idle_wires=True, with_layout=True, fold=None, ax=None, initial_state=False, cregbundle=True) + +#========================================================================# +#============= START THE ALGORITHM FOR DIFFERENT U VALUES ===============# +#========================================================================# +for R in interdist_list: + + psi4.core.clean() + # Define the geometry + psi4.core.set_output_file(working_directory + "examples/results/H{}_R{}_{}_{}_Psi4.dat".format(n_orbs,R,basis,functional),True) + psi4.geometry(QDFT.Hchain_geometry("linear",n_orbs,R)) + + psi4.set_options({'basis': basis, 'scf_type': 'pk'})#, 'print': 5, 'debug': 1}) + dft_e, dft_wfn = psi4.energy(functional, return_wfn=True) + # Hcore and J (Coulomb) matrices: + Hcore = dft_wfn.H().clone() + E_nuc = dft_wfn.get_energies('Nuclear') + # Density matrix in the AO basis: + D_AO_DFT = dft_wfn.Da().clone() + # Overlap matrix in the AO basis: + S_AO = dft_wfn.S().np + # KS-MO coefficient matrix in the AO basis: + C_AO = dft_wfn.Ca().np + # Compute the inverse square root of the overlap matrix S + S_eigval, S_eigvec = np.linalg.eigh(S_AO) + S_sqrt_inv = S_eigvec @ np.diag((S_eigval)**(-1./2.)) @ S_eigvec.T + C_transformation = np.linalg.inv(S_sqrt_inv) + C_OAO = C_transformation @ C_AO + proba_states_exact = [np.abs(C_OAO[:,state])**2 for state in range(n_occ)] + + # Construct the SAD Guess for the initial density D_AO + psi4.core.prepare_options_for_module("SCF") + sad_basis_list = psi4.core.BasisSet.build(dft_wfn.molecule(), "ORBITAL", + psi4.core.get_global_option("BASIS"), + puream=dft_wfn.basisset().has_puream(), + return_atomlist=True) + sad_fitting_list = psi4.core.BasisSet.build(dft_wfn.molecule(), "DF_BASIS_SAD", + psi4.core.get_option("SCF", "DF_BASIS_SAD"), + puream=dft_wfn.basisset().has_puream(), + return_atomlist=True) + + # Use Psi4 SADGuess object to build the SAD Guess + SAD = psi4.core.SADGuess.build_SAD(dft_wfn.basisset(), sad_basis_list) + SAD.set_atomic_fit_bases(sad_fitting_list) + SAD.compute_guess() + D_AO = SAD.Da() + + if run_fci: + psi4.set_options({'ci_maxiter': 100}) + fci_e = psi4.energy('fci', return_wfn=False) + + # Initialize the potential object + V_xc = dft_wfn.Da().clone() + Vpotential = dft_wfn.V_potential() + + mints = psi4.core.MintsHelper(dft_wfn.basisset()) + I = np.asarray(mints.ao_eri()) + + output_file = working_directory + "examples/results/H{}_R{}_{}_{}_nshots{}_layer{}_maxiter{}_resampling{}_slopeSPSA{}_slopeSCF{}_{}.dat".format(n_orbs,R,basis,opt_method,nshots,n_blocks,opt_maxiter,resampling,slope_SPSA,slope_SCF,simulation) + + print("*"*50) + print("*" + " "*18 + "R = {:8.3f}".format(R) + " "*18 + "*") + print("*"*50) + with open(output_file,'w') as f: f.write('') + + Etot = 0 + Delta_E = 1e8 + dRMS = 1e8 + Fock_list = [] + DIIS_error = [] + + with open(output_file,'a') as f: f.write(str(circuits[0].decompose())+"\n") + + last_energies = [] + for SCF_ITER in range(1,SCF_maxiter+1): + + print("iteration ",SCF_ITER) + + # Compute the Coulomb potential + J_coulomb = np.einsum('pqrs,rs->pq', I, D_AO.np) + # Compute the XC potential with the new density matrix in the AO basis + Vpotential.set_D([D_AO]) + Vpotential.compute_V([V_xc]) + + # Compute the Fock matrix in the AO basis: + F_AO = Hcore.np + 2*J_coulomb + V_xc.np + + # DIIS + diis_e = np.einsum('ij,jk,kl->il', F_AO, D_AO.np, S_AO) - np.einsum('ij,jk,kl->il', S_AO, D_AO.np, F_AO) + diis_e = S_sqrt_inv @ diis_e @ S_sqrt_inv + Fock_list.append(F_AO) + DIIS_error.append(diis_e) + dRMS = np.mean(diis_e**2)**0.5 + param_values = param_values + np.random.rand(n_param)/10000. + + if SCF_ITER >= 2: + # Limit size of DIIS vector + diis_count = len(Fock_list) + if diis_count > 6: + # Remove oldest vector + del Fock_list[0] + del DIIS_error[0] + diis_count -= 1 + + # Build error matrix B, [Pulay:1980:393], Eqn. 6, LHS + B = np.empty((diis_count + 1, diis_count + 1)) + B[-1, :] = -1 + B[:, -1] = -1 + B[-1, -1] = 0 + for num1, e1 in enumerate(DIIS_error): + for num2, e2 in enumerate(DIIS_error): + if num2 > num1: continue + val = np.einsum('ij,ij->', e1, e2) + B[num1, num2] = val + B[num2, num1] = val + + # normalize + B[:-1, :-1] /= np.abs(B[:-1, :-1]).max() + + # Build residual vector, [Pulay:1980:393], Eqn. 6, RHS + resid = np.zeros(diis_count + 1) + resid[-1] = -1 + + # Solve Pulay equations, [Pulay:1980:393], Eqn. 6 + ci = np.linalg.solve(B, resid) + + # Calculate new fock matrix as linear + # combination of previous fock matrices + F_AO = np.zeros_like(F_AO) + for num, c in enumerate(ci[:-1]): + F_AO += c * Fock_list[num] + + # Build the Fock matrix in the OAO basis: + F_OAO = S_sqrt_inv @ F_AO @ S_sqrt_inv + + # Map the non-interacting Hamiltonian (Fock matrix) in the OAO basis into Qubit Hamiltonian + H_qubit, IJ_op_list, full_pauliop_list, full_pauli_list = QDFT.transformation_Hmatrix_Hqubit(F_OAO,n_qubits) + solver = NumPyEigensolver(k = 2**n_qubits) + result = solver.compute_eigenvalues(H_qubit) + eigvals = result.eigenvalues + eigvecs = result.eigenstates.to_matrix().T + + with open(output_file,'a') as f: + f.write("varepsilon exact: {}".format(dft_wfn.epsilon_a().np[:]) + "\n") + f.write("varepsilon from diag: {}".format(eigvals[:n_occ]) + "\n") + f.write("weights: {}".format(weights) + "\n") + + if opt_method == "L-BFGS-B": opt_options = {'maxiter': opt_maxiter,'ftol':ftol,'gtol':gtol} + if opt_method == "SLSQP": opt_options = {'maxiter': opt_maxiter,'ftol':ftol} + if opt_method != "SPSA": + if not basinhopping: + f_min = scipy.optimize.minimize(QDFT.cost_function_energy, + x0 = (param_values), + args = (circuits, + H_qubit, + weights, + simulation, + nshots, + backend, + False), + method = opt_method, + options = opt_options) + else: + f_min = scipy.optimize.basinhopping(QDFT.cost_function_energy, + x0 = (param_values), + niter = niter_hopping, # niter steps of basinhopping, and niter+1 optimizer iterations for each step. + minimizer_kwargs = {'method': opt_method, + 'args': (circuits, + H_qubit, + weights, + simulation, + nshots, + backend, + False)}) + param_values = f_min['x'] + E_SA = f_min['fun'] + else: + spsa = SPSA(maxiter=opt_maxiter,blocking=blocking,allowed_increase=allowed_increase,last_avg=slope_SPSA,resamplings=resampling) + cost_function = SPSA.wrap_function(QDFT.cost_function_energy, + (circuits, + H_qubit, + weights, + simulation, + nshots, + backend, + False)) + result = spsa.minimize(cost_function, x0=param_values) + param_values = result.x + E_SA = result.fun # if last_avg is not 1, it returns the callable function with the last_avg param_values as input ! This seems generally good and better than taking the mean of the last_avg function calls. + stddev = spsa.estimate_stddev(cost_function, initial_point=param_values) + + bounds = [circuits[state].bind_parameters(param_values) for state in range(n_occ)] + if nshots is not False: + energies = [sampled_expectation_value(bounds[state],H_qubit,backend,simulation,nshots=nshots) for state in range(n_occ)] + else: + energies = [np.real((StateFn(H_qubit, is_measurement=True) @ StateFn(bounds[state])).eval()) for state in range(n_occ)] + + with open(output_file,'a') as f: f.write("SA ENERGY: {}".format(E_SA) + "\n") + + if simulation == "noiseless": + bounds = [circuits[state].bind_parameters(param_values) for state in range(n_occ)] + states = [np.array(Statevector(bounds[state])) for state in range(n_occ)] + if nshots is not False: + #states = [sampled_state(bounds[state],n_qubits,simulation,backend=backend,nshots=nshots) for state in range(n_occ)] # Grover... actually not needed. + proba_states = [np.random.multinomial(nshots,(np.abs(states[state])**2)) / nshots for state in range(n_occ)] + else: # state vector simulation + proba_states = [np.abs(states[state])**2 for state in range(n_occ)] + elif simulation == "noisy": + states = [] + proba_states = [] + for state in range(n_occ): + bound = circuits[state].bind_parameters(param_values) + bound.save_statevector() + # Transpile for simulator + bound = transpile(bound, backend) + result = backend.run(bound).result() + state = result.get_statevector(bound) + proba_state = np.random.multinomial(nshots,(np.abs(state)**2)) / nshots + states.append(state) + proba_states.append(proba_state) + else: + sys.exit("Simulation argument '{}' does not exist".format(simulation)) + + # evaluate all the expectation values of Pauli strings to compute the density: + bounds = [circuits[state].bind_parameters(param_values) for state in range(n_occ)] + all_exp_values = np.zeros((n_occ,len(full_pauliop_list))) + for state in range(n_occ): + for pauli in range(len(full_pauliop_list)): + if nshots is not False: + all_exp_values[state,pauli] = sampled_expectation_value(bounds[state],full_pauliop_list[pauli],backend,simulation,nshots=nshots) + else: + all_exp_values[state,pauli] = np.real((StateFn(full_pauliop_list[pauli], is_measurement=True) @ StateFn(bounds[state])).eval()) + + # Use the expectation values of Pauli strings to estimate the expectation value of |Phi_I> < Phi_J | + h.c. )| psi_k > = 2 Re( c_Ik c_Jk ) + product_coeff[occ,ij] += pauli.coeffs[0] * all_exp_values[occ,full_pauli_list.index(pauli.to_pauli_op().primitive)] / 2. + + # Compute the density matrix + D_OAO = np.zeros((n_orbs,n_orbs)) + for i in range(n_orbs): + for j in range(i+1): + for occ in range(n_occ): + ij = i*(i+1)//2 + j + D_OAO[i,j] += product_coeff[occ,ij] + D_OAO[j,i] = D_OAO[i,j] + D_AO.np[:] = S_sqrt_inv @ D_OAO @ S_sqrt_inv + + # Compute the Hxc energy with the new density: + Hxcpot_energy = 2*np.einsum('pq,pq->', (J_coulomb + V_xc.np), D_AO.np) # factors 2 because D_AO is only alpha-D_AO. + Vpotential.set_D([D_AO]) + Vpotential.compute_V([V_xc]) # otherwise it doesn't change the EHxc energy... + EHxc = Vpotential.quadrature_values()["FUNCTIONAL"] + # Compute the Hxc potential contribution (old potential * new density !) + Etot_new = 2*np.sum(energies) + EHxc - Hxcpot_energy + E_nuc + Delta_E = abs(Etot_new - Etot) + Etot = Etot_new + + fidelities = np.array([abs(np.conj(states[i]).T @ C_OAO[:,i])**2 for i in range(n_occ)]) + if isinstance(fidelities,int): fidelities = [fidelities] + if nshots is not False: + fidelities_proba = np.array([np.linalg.norm(proba_states[i] - proba_states_exact[i]) for i in range(n_occ)]) + if isinstance(fidelities_proba,int): fidelities_proba = [fidelities_proba] + + # Print results + with open(output_file,'a') as f: + f.write("*" * 10 + " ITERATION {:3d} ".format(SCF_ITER) + "*" * 10 + "\n") + f.write("Energy (hartree) : {:16.8f}".format(Etot) + "\n") + f.write("Occupied KS nrj : {}".format(energies) + "\n") + f.write("Fidelity wrt ED : {}".format([abs(np.conj(states[i]).T @ eigvecs[:, i]) ** 2 for i in range(n_occ)]) + "\n") + f.write("Fidelity wrt KS : {}".format(fidelities) + "\n") + if nshots is not False: f.write("DiffNorm proba : {}".format(fidelities_proba) + "\n") + f.write("Delta E iter : {:16.8f}".format(Delta_E) + "\n") + f.write("Delta E DFTexact : {:16.8f}".format(Etot - dft_e) + "\n") + f.write("dRMS : {:16.8f}\n".format(dRMS) + "\n") + + if slope_SCF is not None: + last_energies.append(Etot) + + if len(last_energies) > slope_SCF: + last_energies = last_energies[-slope_SCF:] + pp = np.polyfit(range(slope_SCF), last_energies, 1) + slope = pp[0] + origin_intersection = pp[1] + with open(output_file,'a') as f: + f.write("Slope SCF : {:16.8f}".format(slope) + "\n") + f.write("Origin Inters. : {:16.8f}\n".format(origin_intersection) + "\n") + + if abs(slope) < 5e-4: + Delta_E = 0. + normocc = 0. + + if ((Delta_E < E_conv) and (dRMS < D_conv)) or SCF_ITER == SCF_maxiter: + + if run_fci: rel_error_EVQE_EFCI = abs((Etot-fci_e)/fci_e) + if run_fci: rel_error_EDFT_EFCI = abs((dft_e-fci_e)/fci_e) + rel_error_EVQE_EDFT = abs((Etot-dft_e)/dft_e) + rel_error_DVQE_DDFT = abs((np.linalg.norm(D_AO.np) - np.linalg.norm(D_AO_DFT.np))/np.linalg.norm(D_AO_DFT.np)) + + with open(output_file,'a') as f: + if SCF_ITER == SCF_maxiter: f.write("*"*10 + " FAILURE " + "*"*10 + "\n") + if (Delta_E < E_conv) and (dRMS < D_conv): f.write("*"*10 + " SUCCESS " + "*"*10 + "\n") + f.write("Iteration : {:16d}".format(SCF_ITER) + "\n") + f.write("States fidelity : {}".format(fidelities) + "\n") + if nshots is not False: f.write("DiffNorm proba : {}".format(fidelities_proba) + "\n") + f.write("Rel Err DVQE/DDFT: {:16.8f}".format(rel_error_DVQE_DDFT) + "\n") + f.write("Rel Err EVQE/EDFT: {:16.8f}".format(rel_error_EVQE_EDFT) + "\n") + if run_fci: + f.write("Rel Err EVQE/EFCI: {:16.8f}".format(rel_error_EVQE_EFCI) + "\n") + f.write("Rel Err EDFT/EFCI: {:16.8f}".format(rel_error_EDFT_EFCI) + "\n") + f.write("FCI energy : {:16.8f}".format(fci_e) + "\n") + f.write("DFT energy exact : {:16.8f}".format(dft_e) + "\n") + f.write("DFT energy SAVQE : {:16.8f}".format(Etot.real) + "\n") + if opt_method == "SPSA": f.write("stddev SPSA : {:16.8f}".format(stddev) + "\n") + break + diff --git a/examples/QDFT_Hubbard.py b/examples/QDFT_Hubbard.py new file mode 100755 index 0000000..cd2c00a --- /dev/null +++ b/examples/QDFT_Hubbard.py @@ -0,0 +1,363 @@ +import sys, os +import numpy as np +import math +import scipy +import subprocess +import QDFT +import SOFT +# importing Qiskit +from qiskit import Aer, transpile +from qiskit import QuantumCircuit, ClassicalRegister +from qiskit.quantum_info import Statevector +from qiskit.algorithms.optimizers import SPSA +from qiskit.algorithms import NumPyEigensolver +from qiskit.circuit.library import TwoLocal +from qiskit.providers.aer import QasmSimulator, AerSimulator +from qiskit.providers.aer.noise import NoiseModel +from qiskit.providers.fake_provider import FakeVigo +from qiskit.visualization import circuit_drawer +from qiskit.opflow import StateFn + +working_directory = os.getenv('QDFT_DIR') + +#===========================================================# +#=============== Initialization by the user ================# +#===========================================================# + +# Quantum Simulator +nshots = False +simulation = ["noiseless","noisy"][0] # noiseless doesn't mean without sampling noise ! Noisy means a real simulation of a quantum computer. +if simulation == "noiseless": + backend = Aer.get_backend('statevector_simulator') + blocking = False + allowed_increase = None +elif simulation == "noisy": + #device_backend = FakeVigo() + #device = QasmSimulator.from_backend(device_backend) + #noise_model = NoiseModel.from_backend(device) + import qiskit.providers.aer.noise as noise + # Error probabilities + prob_1 = 0.0001 # 1-qubit gate + prob_2 = 0.001 # 2-qubit gate + # Depolarizing quantum errors + error_1 = noise.depolarizing_error(prob_1, 1) + error_2 = noise.depolarizing_error(prob_2, 2) + # Add errors to noise model + noise_model = noise.NoiseModel() + noise_model.add_all_qubit_quantum_error(error_1, ['u1','u2','u3','ry','rz','sx','rx']) + noise_model.add_all_qubit_quantum_error(error_2, ['cx','cz']) + + backend = QasmSimulator(method='statevector', noise_model=noise_model) + blocking = True + allowed_increase = 0.1 +else: + sys.exit("Simulation argument '{}' does not exist".format(simulation)) + +n_blocks = 2 # Number of block considered into the Hardware efficient ansatz +rotation_blocks= ['ry','rz',['ry','rz'],['rz','rx','rz']][0] # rotation gates in the ansatz ### I noticed RyRz works best (3 states optimization for instance, but much longer time...) +entanglement = ['full','linear','circular','sca'][1] # the way qubits are entangled in the ansatz +entanglement_blocks = ['cz','cx'][1] # entanglement gate in the ansatz ## I noticed cz works best (3 states optimization) + +# SCF convergence criteria: +E_conv = 1e-5 +D_conv = 1e-4 +SCF_maxiter = 50 +slope_SCF = 5 # set to None is not used. + +# Classical optimizer criteria: +basinhopping = False +niter_hopping = 10 # parameter used for basinhopping +opt_method = ['L-BFGS-B','SLSQP','SPSA'][0] # classical optimizer +opt_maxiter = 1000 # number of iterations of the classical optimizer +ftol = 2.220446049250313e-9 +gtol = 1e-5 +resampling = 2 # used for SPSA, see documentation. +slope_SPSA = 25 # set to None is not used. + +# System: +n_qubits = 2 +n_sites = 2**n_qubits +n_elec = 2 +n_occ = n_elec//2 +t = 1. +#U_list = [0.0001,1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0] +U_list = [10.0] +potential = ["uniform","random","ABAB","power","decreasing"][4] + +# SA weights: +weights_choice = ["equi","decreasing","equi_periodic","equi_antiperiodic"][1] +automatic_weight_choice = False + +#===========================================================# +#=============== End of the initialization =================# +#===========================================================# + +if automatic_weight_choice: + if (n_elec//2)%2 == 1: weights_choice = "equi_periodic" + if (n_elec//2)%2 == 0: weights_choice = "equi_antiperiodic" + +if weights_choice == "equi": + weights = [1./n_occ for i in range(n_occ)] # should be n_occ of them +elif weights_choice == "decreasing": + weights = [(1.+i)/(n_occ*(n_occ+1.)/2) for i in reversed(range(n_occ))] # should be n_occ of them +elif weights_choice == "equi_antiperiodic": + weights = [] + for i in reversed(range(n_occ//2)): + weights.append((1.+i)/((n_occ//2)*((n_occ//2)+1.))) + weights.append((1.+i)/((n_occ//2)*((n_occ//2)+1.))) +elif weights_choice == "equi_periodic": + first_weight = (1.+(n_occ-1))/(n_occ*(n_occ+1.)/2) + weights = [first_weight] + for i in reversed(range(n_occ//2)): + weights.append((1.+i)/((n_occ//2)*((n_occ//2)+1.)) - first_weight/(n_occ-1)) + weights.append((1.+i)/((n_occ//2)*((n_occ//2)+1.)) - first_weight/(n_occ-1)) +else: + sys.exit("The choice for the weights-attribution is not defined.") + +#===========================================================# +#============= External (physical) potential ===============# +#===========================================================# + +v = np.full((n_sites),1.*n_elec/(1.*n_sites)) # uniform potential +if potential == "uniform": + pass +elif potential == "random": + for i in range(n_sites): v[i] = random.uniform(-1,1) +elif potential == "ABAB": + for i in range(n_sites//2): + v[2*i] = -1. + v[2*i+1] = +1. +elif potential == "power": # See GAO XIANLONG et al. PHYSICAL REVIEW B 73, 165120 (2006) + l = 2 + const_V = 0.006 + for i in range(n_sites): + v[i] = const_V*(i - n_sites/2)**l +elif potential == "decreasing": + for i in reversed(range(n_sites)): + v[i] = 0.1*i +else: + sys.exit("The potential is not defined. Program terminated.") + +#===========================================================# +#================ CIRCUIT IMPLEMENTATION ===================# +#===========================================================# + +# Preparation of orthogonal initial states via different initial circuits (need n_occ of them !) +initial_circuits = [] +for i in range(n_occ): initial_circuits += [QuantumCircuit(n_qubits)] + +for state in range(n_occ): # binarystring representation of the integer + for i in QDFT.list_of_ones(state,n_qubits): + initial_circuits[state].x(i) + +circuits = [TwoLocal(n_qubits,rotation_blocks,entanglement_blocks,entanglement,n_blocks,insert_barriers=True,initial_state=initial_circuits[state]) for state in range(n_occ)] +n_param = circuits[0].num_parameters +param_values= np.zeros(n_param) +print(circuits[0].decompose()) +circuit_drawer(circuits[0].decompose(), scale=None, filename="circuit", style=None, output="latex_source", interactive=True, plot_barriers=True, reverse_bits=False, justify=None, vertical_compression='medium', idle_wires=True, with_layout=True, fold=None, ax=None, initial_state=False, cregbundle=True) +#========================================================================# +#============= START THE ALGORITHM FOR DIFFERENT U VALUES ===============# +#========================================================================# +for U in U_list: + + print("*"*50) + print("*" + " "*18 + "U = {:8.3f}".format(U) + " "*18 + "*") + print("*"*50) + + output_file = working_directory + "examples/results/L{}_N{}_U{}_{}_{}_nshots{}_layer{}_maxiter{}_resampling{}_slopeSPSA{}_slopeSCF{}_{}.dat".format(n_sites,n_elec,U,opt_method,potential,nshots,n_blocks,opt_maxiter,resampling,slope_SPSA,slope_SCF,simulation) + with open(output_file,'w+') as f: f.write('') + + # perform DFT to compute fidelities wrt exact KS orbitals + KS_orbs, E_DFT, density_exact = SOFT.run_SOFT_Hubbard(n_sites,n_elec,U,t,v,SCF_maxiter,working_directory,output_file) + proba_states_exact = [np.abs(KS_orbs[:,state])**2 for state in range(n_occ)] + + density = np.full((n_sites),1.*n_elec/(1.*n_sites)) + mix_cst = 0.4 + Etot = 0 + Delta_E = 1e8 + + with open(output_file,'a') as f: f.write(str(circuits[0].decompose())+"\n") + + last_energies = [] + for SCF_ITER in range(1,SCF_maxiter+1): + + print("iteration ",SCF_ITER) + + # Compute the Hxc BALDA potential: + subprocess.check_call("echo " + str(U) + " " + str(t) + " | beta_and_derivatives",shell=True, cwd = working_directory + "/examples/results/") + with open(working_directory + "/examples/results/beta_dbetadU.dat","r") as f: + line = f.read() + beta = float(line.split()[0]) + dbeta_dU = float(line.split()[1]) + f.close() + deHxc_dn = SOFT.generate_potential(n_sites,U,t,density,beta,dbeta_dU)[1] + + # Build the reference non-interacting 1D Hubbard Hamiltonian with periodic or antiperiodic conditions. + h_KS = SOFT.generate_hamiltonian(n_sites,n_elec,t,v+deHxc_dn) + H_qubit = QDFT.transformation_Hmatrix_Hqubit(h_KS,n_qubits)[0] + solver = NumPyEigensolver(k = 2**n_qubits) + result = solver.compute_eigenvalues(H_qubit) + eigvals = result.eigenvalues + eigvecs = result.eigenstates.to_matrix().T + + with open(output_file,'a') as f: + f.write("varepsilon from diag: {}".format(eigvals[:n_occ]) + "\n") + f.write("weights: {}".format(weights) + "\n") + + if opt_method == "L-BFGS-B": opt_options = {'maxiter': opt_maxiter,'ftol':ftol,'gtol':gtol} + if opt_method == "SLSQP": opt_options = {'maxiter': opt_maxiter,'ftol':ftol} + if opt_method != "SPSA": + if not basinhopping: + f_min = scipy.optimize.minimize(QDFT.cost_function_energy, + x0 = (param_values), + args = (circuits, + H_qubit, + weights, + simulation, + nshots, + backend, + False), + method = opt_method, + options = opt_options) + else: + f_min = scipy.optimize.basinhopping(QDFT.cost_function_energy, + x0 = (param_values), + niter = niter_hopping, # niter steps of basinhopping, and niter+1 optimizer iterations for each step. + minimizer_kwargs = {'method': opt_method, + 'args': (circuits, + H_qubit, + weights, + simulation, + nshots, + backend, + False)}) + param_values = f_min['x'] + E_SA = f_min['fun'] + else: + spsa = SPSA(maxiter=opt_maxiter,blocking=blocking,allowed_increase=allowed_increase,last_avg=slope_SPSA,resamplings=resampling) + cost_function = SPSA.wrap_function(QDFT.cost_function_energy, + (circuits, + H_qubit, + weights, + simulation, + nshots, + backend, + False)) + result = spsa.minimize(cost_function, x0=param_values) + param_values = result.x + E_SA = result.fun # if last_avg is not 1, it returns the callable function with the last_avg param_values as input ! This seems generally good and better than taking the mean of the last_avg function calls. + stddev = spsa.estimate_stddev(cost_function, initial_point=param_values) + + bounds = [circuits[state].bind_parameters(param_values) for state in range(n_occ)] + if nshots is not False: + energies = [sampled_expectation_value(bounds[state],H_qubit,backend,simulation,nshots=nshots) for state in range(n_occ)] + else: + energies = [np.real((StateFn(H_qubit, is_measurement=True) @ StateFn(bounds[state])).eval()) for state in range(n_occ)] + + with open(output_file,'a') as f: f.write("SA ENERGY: {}".format(E_SA) + "\n") + + # SOFT vs DFT: no need to have the full state, just the probability density ! + if simulation == "noiseless": + bounds = [circuits[state].bind_parameters(param_values) for state in range(n_occ)] + states = [np.array(Statevector(bounds[state])) for state in range(n_occ)] + if nshots is not False: + #states = [sampled_state(bounds[state],n_qubits,simulation,backend=backend,nshots=nshots) for state in range(n_occ)] # Grover... actually not needed. + proba_states = [np.random.multinomial(nshots,(np.abs(states[state])**2)) / nshots for state in range(n_occ)] + else: # state vector simulation + proba_states = [np.abs(states[state])**2 for state in range(n_occ)] + elif simulation == "noisy": + states = [] + proba_states = [] + for state in range(n_occ): + bound = circuits[state].bind_parameters(param_values) + bound.save_statevector() + # Transpile for simulator + bound = transpile(bound, backend) + result = backend.run(bound).result() + state = result.get_statevector(bound) + proba_state = np.random.multinomial(nshots,(np.abs(state)**2)) / nshots + states.append(state) + proba_states.append(proba_state) + else: + sys.exit("Simulation argument '{}' does not exist".format(simulation)) + + # Compute the density + density_new = np.zeros((n_sites)) + for orbital in range(n_sites): + for occ in range(n_occ): + density_new[orbital] += 2*proba_states[occ][orbital] + + # Compute the Hxc energy contribution with the new density + eHxc = SOFT.generate_potential(n_sites,U,t,density_new,beta,dbeta_dU)[0] + + # Compute the total KSDFT energy + sum_occ_eKS = 0 + sum_eHxc = 0 + sum_deHxc_dn = 0 + for i in range(n_occ): sum_occ_eKS += 2.*energies[i] + for i in range(n_sites): sum_eHxc += eHxc[i] + for i in range(n_sites): sum_deHxc_dn+= deHxc_dn[i]*density_new[i] + Etot_new = sum_occ_eKS + sum_eHxc - sum_deHxc_dn + Delta_E = abs(Etot_new - Etot) + Etot = Etot_new + # Compute the norm of the change of density between the iterations. + normocc = np.linalg.norm(density - density_new) + # Compute the norm of the change of density between the iteration and the exact one: + normocc_exact = np.linalg.norm(density - density_exact) + + # Check convergence and increase mix_cst if reached + #if (Delta_E <= SCF_energy_tol and normocc <= SCF_density_tol): mix_cst += 0.2 + # Kind of DIIS algorithm: reduce the change in density to avoid convergence issues + density = (1 - mix_cst)*density + mix_cst*density_new + + fidelities = np.array([abs(np.conj(states[i]).T @ KS_orbs[:,i])**2 for i in range(n_occ)]) + if isinstance(fidelities,int): fidelities = [fidelities] + if nshots is not False: + fidelities_proba = np.array([np.linalg.norm(proba_states[i] - proba_states_exact[i]) for i in range(n_occ)]) + if isinstance(fidelities_proba,int): fidelities_proba = [fidelities_proba] + # Print results + with open(output_file,'a') as f: + f.write("*" * 10 + " ITERATION {:3d} ".format(SCF_ITER) + "*" * 10 + "\n") + f.write("Energy (hartree) : {:16.8f}".format(Etot) + "\n") + f.write("Occupied KS nrj : {}".format(energies) + "\n") + f.write("New density : {}".format(density_new) + "\n") + f.write("Damped density : {}".format(density) + "\n") + f.write("Fidelity wrt ED : {}".format([abs(np.conj(states[i]).T @ eigvecs[:, i]) ** 2 for i in range(n_occ)]) + "\n") + f.write("Fidelity wrt KS : {}".format(fidelities) + "\n") + if nshots is not False: f.write("DiffNorm proba : {}".format(fidelities_proba) + "\n") + f.write("Delta E iter : {:16.8f}".format(Delta_E) + "\n") + f.write("Delta E DFTexact : {:16.8f}".format(Etot - E_DFT) + "\n") + f.write("Norm Delta_occ : {:16.8f}".format(normocc) + "\n") + f.write("Norm Delta_occ_exact: {:16.8f}".format(normocc_exact) + "\n") + + if slope_SCF is not None: + last_energies.append(Etot) + + if len(last_energies) > slope_SCF: + last_energies = last_energies[-slope_SCF:] + pp = np.polyfit(range(slope_SCF), last_energies, 1) + slope = pp[0] + origin_intersection = pp[1] + with open(output_file,'a') as f: + f.write("Slope SCF : {:16.8f}".format(slope) + "\n") + f.write("Origin Inters. : {:16.8f}\n".format(origin_intersection) + "\n") + + if abs(slope) < 5e-4: + Delta_E = 0. + normocc = 0. + + if ((Delta_E < E_conv) and (normocc < D_conv)) or SCF_ITER == SCF_maxiter: + + rel_error_EVQE_EDFT = abs((Etot-E_DFT)/E_DFT) + + with open(output_file,'a') as f: + if SCF_ITER == SCF_maxiter: f.write("*"*10 + " FAILURE " + "*"*10 + "\n") + if (Delta_E < E_conv) and (normocc < D_conv): f.write("*"*10 + " SUCCESS " + "*"*10 + "\n") + f.write("Iteration : {:16d}".format(SCF_ITER) + "\n") + f.write("States fidelity : {}".format(fidelities) + "\n") + if nshots is not False: f.write("DiffNorm proba : {}".format(fidelities_proba) + "\n") + f.write("Rel Err EVQE/EDFT: {:16.8f}".format(rel_error_EVQE_EDFT) + "\n") + f.write("DFT energy exact : {:16.8f}".format(E_DFT) + "\n") + f.write("DFT energy SAVQE : {:16.8f}".format(Etot) + "\n") + if opt_method=="SPSA": f.write("stddev SPSA : {:16.8f}".format(stddev) + "\n") + break diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..a61b52d --- /dev/null +++ b/setup.py @@ -0,0 +1,13 @@ +import io +from setuptools import setup, find_packages + +setup( + name='QDFT', + author='Bruno Senjean', + author_email='bruno.senjean@umontpellier.fr', + url='', + description=('Kohn-Sham Density Functional Theory on Quantum Computers'), + packages=find_packages(where='src'), + package_dir={'': 'src'}, + include_package_data=True, +) diff --git a/src/QDFT.egg-info/PKG-INFO b/src/QDFT.egg-info/PKG-INFO new file mode 100644 index 0000000..3b5447b --- /dev/null +++ b/src/QDFT.egg-info/PKG-INFO @@ -0,0 +1,13 @@ +Metadata-Version: 2.1 +Name: QDFT +Version: 0.0.0 +Summary: Kohn-Sham Density Functional Theory on Quantum Computers +Home-page: UNKNOWN +Author: Bruno Senjean +Author-email: bruno.senjean@umontpellier.fr +License: UNKNOWN +Platform: UNKNOWN +License-File: LICENSE + +UNKNOWN + diff --git a/src/QDFT.egg-info/SOURCES.txt b/src/QDFT.egg-info/SOURCES.txt new file mode 100644 index 0000000..b05714b --- /dev/null +++ b/src/QDFT.egg-info/SOURCES.txt @@ -0,0 +1,11 @@ +LICENSE +README.md +setup.py +src/QDFT/__init__.py +src/QDFT/geometry.py +src/QDFT/measurements.py +src/QDFT/operators.py +src/QDFT.egg-info/PKG-INFO +src/QDFT.egg-info/SOURCES.txt +src/QDFT.egg-info/dependency_links.txt +src/QDFT.egg-info/top_level.txt \ No newline at end of file diff --git a/src/QDFT.egg-info/dependency_links.txt b/src/QDFT.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/QDFT.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/src/QDFT.egg-info/top_level.txt b/src/QDFT.egg-info/top_level.txt new file mode 100644 index 0000000..838bde1 --- /dev/null +++ b/src/QDFT.egg-info/top_level.txt @@ -0,0 +1 @@ +QDFT diff --git a/src/QDFT/__init__.py b/src/QDFT/__init__.py new file mode 100644 index 0000000..81bed15 --- /dev/null +++ b/src/QDFT/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 + +from .geometry import Hchain_geometry + +from .operators import ( + transformation_Hmatrix_Hqubit, + sz_operator, + s2_operator) + +from .measurements import ( + list_of_ones, + cost_function_energy, + Grover_diffusion_circuit, + sampled_state, + sampled_expectation_value) + diff --git a/src/QDFT/__pycache__/__init__.cpython-39.pyc b/src/QDFT/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..803343436e27b14cd10c29996c06b7fc7ce85773 GIT binary patch literal 478 zcmYjNy-ve05Kfw=P5L924lppIEJR{MLWluDCnN-u7t2j9tx;@;vs1M^1+T#qA!TJk z;t81W6%}!kzq{}5bocqhbUF&Wy}o}HCt(nL5czLCPpF7VmOVJy1h7R0&H|%2G9ABQ<7YU+X^vS^VjD1X&Etln+8xggPxrIku2^ewX}|#iW?2Kn-#{hs@27A4rsvT zwmZB=h$jhNXK+ z79-EGPY_dOqCnUdL4kG_2b3QYBm^k|9sVuAF~tPm=lRNCBVSn1Pat%DD;-B@jFtY+ l5^R2dee;-GEPeydrS={CwApsag)(JZ!zHP(q+-&W#Xkgpi_ic7 literal 0 HcmV?d00001 diff --git a/src/QDFT/__pycache__/geometry.cpython-39.pyc b/src/QDFT/__pycache__/geometry.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..54f47e48663c15567bf20f10a5eca0a7b6da8b34 GIT binary patch literal 1233 zcma)5&1=*^6rVSnWY=uB-9w?(DlAf@3#HqN_yI)=7VW8E^wlwLgO&698Pfn8Z?Cz*NkzJBjFZxW|gGYO6_KO^^wLC9C7 zoE{}q)^LWCxG;?Yqp!gttn`>P_3u0B_;%0L&|8c$ZYntM;|$+ZOoM?I6Gr!_v?3A% zD_tV5V4XZ$9q6&fv?GWq)8|O6$8?&^kSaNbTLg2)_{`jQFgv7T5mAjjIt1B>=x*sq zi{Thpd5#Q>kva?is}mcsei18X^$uPZS*$XfFX%fs#&k^f0U6~ihsWMBL(dLP<6G-2Q5-Ro+15GUO6HUoxIWgQ$;JMtY3cJ%|GH7!VCa`5`(%!gknPDV7 z(YD(>NPt;o#=DVYZ+#$A{o23pv^s&$TiYQQyWA12hhAh$z8{2Mq)3FVN9zxsv_k1- z8$?MdFYWh|sSUT|ctRnkHr0smHZFu}P=PvJhO1zrtiv^!h9$U>YV$A;3#ciN+AFQ1 j9xCgYyV(S(kWeT!WqE3uLR(T<^#k_2%kQedUpUDhP6rKZ literal 0 HcmV?d00001 diff --git a/src/QDFT/__pycache__/measurements.cpython-39.pyc b/src/QDFT/__pycache__/measurements.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..78dbbf77d138008922abb692c01ea8e4c23662cd GIT binary patch literal 5815 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 A(EtDd literal 0 HcmV?d00001 diff --git a/src/QDFT/geometry.py b/src/QDFT/geometry.py new file mode 100644 index 0000000..596458e --- /dev/null +++ b/src/QDFT/geometry.py @@ -0,0 +1,31 @@ +def Hchain_geometry(choice,n_hydrogens,R,r_H2=0.7): + + string_geo = "0 1\n" + if choice == "linear": + for d in range(n_hydrogens//2): + string_geo += "H 0. 0. {}\n".format(- (R/2. + d*R)) + string_geo += "H 0. 0. {}\n".format(+ (R/2. + d*R)) + elif choice == "linear_broken": + delta_R = np.random.rand(n_hydrogens)/100. # array of random values between [0,1/100) + for d in range(n_hydrogens//2): + string_geo += "H 0. 0. {}\n".format(- (R/2. + d*R) + delta_R[d]) + for d in range(n_hydrogens//2-1): + string_geo += "H 0. 0. {}\n".format(+ (R/2. + d*R) + delta_R[n_hydrogens//2+d]) + string_geo += "H 0. 0. {}\n".format(+ (R/2. + (n_hydrogens//2-1)*R) + delta_R[n_hydrogens-1]) + elif choice == "dimer_horizontal": + for d in range(n_hydrogens//4): + string_geo += "H 0. 0. {}\n".format(- (R/2. + d*R + r_H2*d)) + string_geo += "H 0. 0. {}\n".format(+ (R/2. + d*R + r_H2*d)) + string_geo += "H 0. 0. {}\n".format(- (R/2. + d*R + r_H2*(d+1))) + string_geo += "H 0. 0. {}\n".format(+ (R/2. + d*R + r_H2*(d+1))) + elif choice == "dimer_vertical": + for d in range(n_hydrogens//4): + string_geo += "H 0. {} {}\n".format(-r_H2/2., - (R/2. + d*R)) + string_geo += "H 0. {} {}\n".format(+r_H2/2., - (R/2. + d*R)) + string_geo += "H 0. {} {}\n".format(-r_H2/2., + (R/2. + d*R)) + string_geo += "H 0. {} {}\n".format(+r_H2/2., + (R/2. + d*R)) + string_geo += "symmetry c1\n" + string_geo += "nocom\n" + string_geo += "noreorient\n" + + return string_geo diff --git a/src/QDFT/measurements.py b/src/QDFT/measurements.py new file mode 100644 index 0000000..db507a7 --- /dev/null +++ b/src/QDFT/measurements.py @@ -0,0 +1,199 @@ +import numpy as np +import math +from qiskit.opflow import StateFn +from qiskit.quantum_info import Statevector +from qiskit import transpile +from qiskit.circuit.library import MCMT +import sys + +def list_of_ones(computational_basis_state: int, n_qubits): + """ + Indices of ones in the binary expansion of an integer in big endian + order. e.g. 010110 -> [1, 3, 4] (which is the reverse of the qubit ordering...) + """ + + bitstring = format(computational_basis_state, 'b').zfill(n_qubits) + + return [abs(j-n_qubits+1) for j in range(len(bitstring)) if bitstring[j] == '1'] + +def cost_function_energy(param_values,circuits,H_qubit,weights,simulation,nshots=False,backend=False,output=False): + + nstates = len(weights) + E_SA = 0. + + bounds = [circuits[state].bind_parameters(param_values) for state in range(nstates)] + if nshots is not False: + energies = [sampled_expectation_value(bounds[state],H_qubit,backend,simulation,nshots=nshots) for state in range(nstates)] + else: + energies = [np.real((StateFn(H_qubit, is_measurement=True) @ StateFn(bounds[state])).eval()) for state in range(nstates)] + + # Compute the state-averaged energy + E_SA = 0. + for i in range(nstates): E_SA += energies[i] * weights[i] + + if output is not False: + with open(output,'a') as f: f.write('{}\n'.format(E_SA)) + + return E_SA + +def Grover_diffusion_circuit(circuit,n_qubits): + """ Construct the Grover diffusion operator circuit. + circuit: QuantumCircuit object + n_qubits: number of qubits + + Returns: the circuit with the added Grover diffusion circuit. + """ + + circuit_Grover = circuit.copy() + for i in range(n_qubits): + circuit_Grover.h(i) + circuit_Grover.x(i) + circuit_Grover += MCMT('z',n_qubits-1,1) + for i in range(n_qubits): + circuit_Grover.x(i) + circuit_Grover.h(i) + + return circuit_Grover + +def sampled_state(original_circuit,n_qubits,simulation,backend=False,nshots=1024): + + print("** WARNING ** This function has not been tested yet.") + circuit = original_circuit.copy() + circuit_Grover = Grover_diffusion_circuit(original_circuit,n_qubits) + if simulation == "noiseless": + state = np.array(Statevector(circuit)) + state_Grover = np.array(Statevector(circuit_Grover)) + # Compute the proba of getting a given bitstring, based on a given distribution defined by the number of shots. + proba_state_array = np.random.multinomial(nshots,(np.abs(state)**2).real) + proba_state_Grover_array = np.random.multinomial(nshots,(np.abs(state_Grover)**2).real) + proba_state = {} + proba_state_Grover = {} + for indice in range(len(proba_state_array)): + if proba_state_array[indice] >= 1: + proba_state[str(indice)] = proba_state_array[indice]/nshots + if proba_state_Grover_array[indice] >= 1: + proba_state_Grover[str(indice)] = proba_state_Grover_array[indice]/nshots + + elif simulation == "noisy": + circuit.measure_all() + circuit_Grover.measure_all() + # Transpile for simulator + circuit = transpile(circuit, backend) + circuit_Grover = transpile(circuit_Grover, backend) + # Run and get counts + result = backend.run(circuit,shots=nshots).result() + counts = result.get_counts(circuit) # The string is little-endian (cr[0] on the right hand side). + # transform the dictionary with binary to integer: + proba_state = {} + for item in counts.items(): + proba_state[str(int(item[0],2))] = item[1]/nshots + # Run and get counts + result = backend.run(circuit_Grover,shots=nshots).result() + counts = result.get_counts(circuit_Grover) # The string is little-endian (cr[0] on the right hand side). + # transform the dictionary with binary to integer: + proba_state_Grover = {} + for item in counts.items(): + proba_state_Grover[str(int(item[0],2))] = item[1]/nshots + + else: + sys.exit("Simulation argument '{}' does not exist".format(simulation)) + + # combine dictionnaries to have the same keys: + combine_dict = proba_state | proba_state_Grover + for key in combine_dict.keys(): + if key not in proba_state: proba_state[key] = 0 + if key not in proba_state_Grover: proba_state_Grover[key] = 0 + + # Find the 4 different values of mean of the coefficients: + mean_Grover = [] + for key in combine_dict: + mean_Grover.append(abs(0.5*( np.sqrt(proba_state[key]) + np.sqrt(proba_state_Grover[key])))) + mean_Grover.append(abs(0.5*(-np.sqrt(proba_state[key]) - np.sqrt(proba_state_Grover[key])))) + mean_Grover.append(abs(0.5*( np.sqrt(proba_state[key]) - np.sqrt(proba_state_Grover[key])))) + mean_Grover.append(abs(0.5*(-np.sqrt(proba_state[key]) + np.sqrt(proba_state_Grover[key])))) + + # Example usage: + around = math.floor(math.log(np.sqrt(nshots), 10)) + 1 + mean_Grover = np.around(mean_Grover,around).tolist() + dict_of_counts = {value:mean_Grover.count(value) for value in mean_Grover} + chosen_one = max(dict_of_counts, key=dict_of_counts.get) + + coefficients = [] + for i in range(2**n_qubits): + key = str(i) + if key in combine_dict: + coefficients.append(( proba_state[key] + 4*chosen_one**2 - proba_state_Grover[key]) / (4*chosen_one)) + else: + coefficients.append(0) + + return coefficients/np.linalg.norm(coefficients) + +def sampled_expectation_value(original_circuit,operator,backend,simulation,nshots=1024): + rot_dic = { 'X' : lambda qubit : circuit.h(qubit), + 'Y' : lambda qubit : circuit.rx(np.pi/2., qubit)} + + try: + nterms = len(operator) + except: + nterms = 1 + nqubits = operator.num_qubits + nshots_per_pauli = int(nshots/nterms) + expectation_value = 0 + if nterms == 1: operator = [operator] + for i in range(nterms): + circuit = original_circuit.copy() + # Begining of the circuit fragment: we detect if Pauli op is X, Y or Z + # and apply a rotation gate accordingly on the associated qubits + # store the places where there is a X, Y or Z operators as well (all rotated to Z) + list_Z = [] + for j in reversed(range(nqubits)): + # For some reason, if str(operator[i].to_pauli_op().primitive) --> ZII, we have + # str(operator[i].to_pauli_op().primitive[0]) = "I" and + # str(operator[i].to_pauli_op().primitive[2]) = "Z".............. + # This messed up so much with my brain. + if str(operator[i].to_pauli_op().primitive[j]) == 'I': + continue + elif str(operator[i].to_pauli_op().primitive[j]) == 'Z': + list_Z.append(j) + else: + rot_dic[str(operator[i].to_pauli_op().primitive[j])](j) + list_Z.append(j) + + # Get the final state of the rotated circuit: + if simulation == "noiseless": + state = np.array(Statevector(circuit)) + # Compute the proba of getting a given bitstring, based on a given distribution defined by the number of shots. + proba_computational_basis_array = np.random.multinomial(nshots_per_pauli,(np.abs(state)**2).real) + proba_computational_basis = {} + for indice in range(len(proba_computational_basis_array)): + if proba_computational_basis_array[indice] >= 1: + proba_computational_basis[str(indice)] = proba_computational_basis_array[indice]/nshots_per_pauli + + elif simulation == "noisy": # if noisy, we cannot get the state directly, we have to combine the measurements to extract the energy. + circuit.measure_all() + # Transpile for simulator + circuit = transpile(circuit, backend) + # Run and get counts + result = backend.run(circuit,shots=nshots_per_pauli).result() + counts = result.get_counts(circuit) # The string is little-endian (cr[0] on the right hand side). + # transform the dictionary with binary to integer: + proba_computational_basis = {} + for item in counts.items(): + proba_computational_basis[str(int(item[0],2))] = item[1]/nshots_per_pauli + + else: + sys.exit("Simulation argument '{}' does not exist".format(simulation)) + + # Compute the energy by combining the proba: + for integer_bitstring in range(2**nqubits): + if str(integer_bitstring) in proba_computational_basis: + phase = 1 + # Determine the phase: + for qubit in list_Z: + if qubit in list_of_ones(integer_bitstring,nqubits): + phase *= -1 + + # Determine the expectation value: + expectation_value += phase * proba_computational_basis[str(integer_bitstring)] * operator[i].to_pauli_op().coeff + + return expectation_value diff --git a/src/QDFT/operators.py b/src/QDFT/operators.py new file mode 100644 index 0000000..8f0829d --- /dev/null +++ b/src/QDFT/operators.py @@ -0,0 +1,77 @@ +from qiskit.opflow import I, X, Y +from qiskit.opflow.primitive_ops import PauliOp + +def transformation_Hmatrix_Hqubit(Hmatrix,nqubits): + """ + This function transforms the Hamiltonian matrix into the qubit Hamiltonian. + Each element of the Hamiltonian is defined in the basis of bitstring configurations, + i.e. | 0000 ... 0 >, | 0000 ... 1 >, etc. Each value "1" corresponds to a qubit excitation + that is given by the operation S^+. + """ + + H_qubit = 0. + IJ_op_list = [] # list of operators for ( |phi_I> + for j in range(len(Hmatrix[0])): + # < J | + IJ_op = I^nqubits + + # First convert the integers into bitstrings: + bitstring_i = bin(i)[2:].zfill(nqubits) + bitstring_j = bin(j)[2:].zfill(nqubits) + + for qubit in range(nqubits): + if int(list(bitstring_i)[qubit]) == 0 and int(list(bitstring_j)[qubit]) == 0: + IJ_op = IJ_op @ ((I^qubit) ^ (((X + 1j*Y)@(X - 1j*Y))/4.) ^ (I^(nqubits - qubit - 1))) + elif int(list(bitstring_i)[qubit]) == 0 and int(list(bitstring_j)[qubit]) == 1: + IJ_op = IJ_op @ ((I^qubit) ^ ( (X + 1j*Y) /2.) ^ (I^(nqubits - qubit - 1))) + elif int(list(bitstring_i)[qubit]) == 1 and int(list(bitstring_j)[qubit]) == 0: + IJ_op = IJ_op @ ((I^qubit) ^ ( (X - 1j*Y) /2.) ^ (I^(nqubits - qubit - 1))) + elif int(list(bitstring_i)[qubit]) == 1 and int(list(bitstring_j)[qubit]) == 1: + IJ_op = IJ_op @ ((I^qubit) ^ (((X - 1j*Y)@(X + 1j*Y))/4.) ^ (I^(nqubits - qubit - 1))) + + H_qubit += float(Hmatrix[i,j]) * IJ_op.reduce() + + IJ_op_adjoint = IJ_op.adjoint() + IJ_op = IJ_op + IJ_op_adjoint + + IJ_op = IJ_op.reduce() + if j < i+1: + IJ_op_list.append(IJ_op) + for num_pauli in range(len(IJ_op.to_pauli_op())): + pauli_string = IJ_op.to_pauli_op()[num_pauli].primitive + if pauli_string not in full_pauli_list: + full_pauliop_list.append(PauliOp(pauli_string)) + full_pauli_list.append(pauli_string) + + return H_qubit.reduce(), IJ_op_list,full_pauliop_list, full_pauli_list + +def sz_operator(n_qubits): + s_z = 0 + + for i in range(n_qubits//2): + s_z += FermionicOp(("N_{}".format(2*i),0.5),register_length=n_qubits) + s_z -= FermionicOp(("N_{}".format(2*i+1),0.5),register_length=n_qubits) + + return s_z + +def s2_operator(n_qubits): + ''' + S2 = S- S+ + Sz(Sz+1) + I use the usual sorting as in OpenFermion, i.e. 1up 1down, 2up 2down, etc... + ''' + s2_op = 0 + s_moins = 0 + s_plus = 0 + s_z = sz_operator(n_qubits) + + for i in range(n_qubits//2): + s_moins += FermionicOp(("+_{} -_{}".format(2*i+1,2*i),1),register_length=n_qubits) + s_plus += FermionicOp(("+_{} -_{}".format(2*i,2*i+1),1),register_length=n_qubits) + + s2_op = s_moins @ s_plus + s_z @ s_z + s_z + return s2_op