def build_sparse_S_b(self, b): b_sparse = [] idx_sparse = [] for I, bI in enumerate(b): if (np.abs(bI) > self._b_thresh): idx_sparse.append(I) b_sparse.append(bI) Idim = len(idx_sparse) self._n_pauli_trm_measures += int(Idim * (Idim + 1) * 0.5) S = np.zeros((len(b_sparse), len(b_sparse)), dtype=complex) Ipsi_qc = qf.QuantumComputer(self._nqb) Ipsi_qc.set_coeff_vec(copy.deepcopy(self._qc.get_coeff_vec())) CI = np.zeros(shape=(Idim, int(2**self._nqb)), dtype=complex) for i in range(Idim): S[i][ i] = 1.0 # With Pauli strings, this is always the inner product Ii = idx_sparse[i] Ipsi_qc.apply_operator(self._sig.terms()[Ii][1]) CI[i, :] = copy.deepcopy(Ipsi_qc.get_coeff_vec()) for j in range(i): S[i][j] = S[j][i] = np.vdot(CI[i, :], CI[j, :]) Ipsi_qc.set_coeff_vec(copy.deepcopy(self._qc.get_coeff_vec())) return idx_sparse, np.real(S), np.real(b_sparse)
def build_b(self): """ Construct the vector b (eq. 5b) of Motta, with h[m] the full Hamiltonian. """ b = np.zeros(self._NI, dtype=complex) denom = np.sqrt(1 - 2 * self._db * self._Ekb[-1]) prefactor = -1.0j / denom self._n_pauli_trm_measures += self._Nl * self._NI Hpsi_qc = qf.QuantumComputer(self._nqb) Hpsi_qc.set_coeff_vec(copy.deepcopy(self._qc.get_coeff_vec())) Hpsi_qc.apply_operator(self._qb_ham) C_Hpsi_qc = copy.deepcopy(Hpsi_qc.get_coeff_vec()) for I, (op_coefficient, operator) in enumerate(self._sig.terms()): Hpsi_qc.apply_operator(operator) exp_val = np.vdot(self._qc.get_coeff_vec(), Hpsi_qc.get_coeff_vec()) b[I] = prefactor * op_coefficient * exp_val Hpsi_qc.set_coeff_vec(C_Hpsi_qc) return np.real(b)
def build_classical_CI_mats(self): """Builds a classical configuration interaction out of single determinants. """ num_tot_basis = len(self._pre_sa_ref_lst) h_CI = np.zeros((num_tot_basis,num_tot_basis), dtype=complex) omega_lst = [] Homega_lst = [] for i, ref in enumerate(self._pre_sa_ref_lst): # NOTE: do NOT use Uprep here (is determinant specific). Un = qforte.QuantumCircuit() for j in range(self._nqb): if ref[j] == 1: Un.add_gate(qforte.gate('X', j, j)) QC = qforte.QuantumComputer(self._nqb) QC.apply_circuit(Un) omega_lst.append(np.asarray(QC.get_coeff_vec(), dtype=complex)) Homega = np.zeros((2**self._nqb), dtype=complex) QC.apply_operator(self._qb_ham) Homega_lst.append(np.asarray(QC.get_coeff_vec(), dtype=complex)) for j in range(len(omega_lst)): h_CI[i][j] = np.vdot(omega_lst[i], Homega_lst[j]) h_CI[j][i] = np.conj(h_CI[i][j]) return h_CI
def perfect_experimental_avg(self, params): """ calculates the exact experimental result of the operator the Experiment object was initialized with :param params: (list) the list of parameters for the state preparation ansatz. """ if(self.prepare_each_time_==False): #1 initialize a quantum computer qc = qforte.QuantumComputer(self.n_qubits_) #2 build/update generator with params # self.generator_.set_parameters(params) #3 apply generator (once if prepare_each_time = False, N_sample times if) qc.apply_circuit(self.generator_) #4 measure operator n_terms = len(self.operator_.terms()) term_sum = 0.0 for k in range(n_terms): term_sum += self.operator_.terms()[k][0] * qc.perfect_measure_circuit(self.operator_.terms()[k][1]); return numpy.real(term_sum) elif(self.prepare_each_time_==True): raise Exception('No support yet for measurement with multiple state preparations')
def test_build_from_openfermion(self): print('\n') trial_state = qforte.QuantumComputer(4) trial_prep = [None] * 5 trial_prep[0] = qforte.gate('H', 0, 0) trial_prep[1] = qforte.gate('H', 1, 1) trial_prep[2] = qforte.gate('H', 2, 2) trial_prep[3] = qforte.gate('H', 3, 3) trial_prep[4] = qforte.gate('cX', 0, 1) trial_circ = qforte.QuantumCircuit() #prepare the circuit for gate in trial_prep: trial_circ.add_gate(gate) # use circuit to prepare trial state trial_state.apply_circuit(trial_circ) test_operator = QubitOperator('X2 Y1', 0.0 - 0.25j) test_operator += QubitOperator('Y2 Y1', 0.25) test_operator += QubitOperator('X2 X1', 0.25) test_operator += QubitOperator('Y2 X1', 0.0 + 0.25j) print(test_operator) qforte_operator = qforte.build_from_openfermion(test_operator) qforte.smart_print(qforte_operator) exp = trial_state.direct_op_exp_val(qforte_operator) print(exp) self.assertAlmostEqual(exp, 0.2499999999999999 + 0.0j)
def build_orb_energies(self): self._orb_e = [] print('\nBuilding single particle energies list:') print('---------------------------------------') qc = qforte.QuantumComputer(self._nqb) qc.apply_circuit(build_Uprep(self._ref, 'reference')) E0 = qc.direct_op_exp_val(self._qb_ham) for i in range(self._nqb): qc = qforte.QuantumComputer(self._nqb) qc.apply_circuit(build_Uprep(self._ref, 'reference')) qc.apply_gate(qforte.gate('X', i, i)) Ei = qc.direct_op_exp_val(self._qb_ham) if (i < sum(self._ref)): ei = E0 - Ei else: ei = Ei - E0 print(f' {i:3} {ei:+16.12f}') self._orb_e.append(ei)
def run(self, beta=1.0, db=0.2, expansion_type='SD', sparseSb=True, b_thresh=1.0e-6, x_thresh=1.0e-10, do_lanczos=False, lanczos_gap=2): self._beta = beta self._db = db self._nbeta = int(beta / db) + 1 self._sq_ham = self._sys.get_sq_hamiltonian() self._expansion_type = expansion_type self._sparseSb = sparseSb self._total_phase = 1.0 + 0.0j self._Uqite = qf.QuantumCircuit() self._b_thresh = b_thresh self._x_thresh = x_thresh self._n_classical_params = 0 self._n_cnot = 0 self._n_pauli_trm_measures = 0 self._do_lanczos = do_lanczos self._lanczos_gap = lanczos_gap qc_ref = qf.QuantumComputer(self._nqb) qc_ref.apply_circuit(self._Uprep) self._Ekb = [np.real(qc_ref.direct_op_exp_val(self._qb_ham))] # Print options banner (should done for all algorithms). self.print_options_banner() # Build expansion pool. self.build_expansion_pool() # Do the imaginary time evolution. self.evolve() if (self._do_lanczos): self.do_qlanczos() # Print summary banner (should done for all algorithms). self.print_summary_banner() # verify that required attributes were defined # (should done for all algorithms). self.verify_run()
def ref_state(self): """ Return a qforte.QuantumComputer instance which represents the Hartree-Fock state of the input molecule """ hf_qc = qforte.QuantumComputer(self.molecule.n_qubits) hf_cir = qforte.QuantumCircuit() for i_a in self.occ_idx_alpha: X_ia = qforte.make_gate('X', i_a, i_a) hf_cir.add_gate(X_ia) for i_b in self.occ_idx_beta: X_ib = qforte.make_gate('X', i_b, i_b) hf_cir.add_gate(X_ib) hf_qc.apply_circuit(hf_cir) return hf_qc
def build_S(self): """ Construct the matrix S (eq. 5a) of Motta. """ Idim = self._NI S = np.zeros((Idim, Idim), dtype=complex) Ipsi_qc = qf.QuantumComputer(self._nqb) Ipsi_qc.set_coeff_vec(copy.deepcopy(self._qc.get_coeff_vec())) # CI[I][J] = (σ_I Ψ)_J CI = np.zeros(shape=(Idim, int(2**self._nqb)), dtype=complex) for i in range(Idim): S[i][ i] = 1.0 # With Pauli strings, this is always the inner product Ipsi_qc.apply_operator(self._sig.terms()[i][1]) CI[i, :] = copy.deepcopy(Ipsi_qc.get_coeff_vec()) for j in range(i): S[i][j] = S[j][i] = np.vdot(CI[i, :], CI[j, :]) Ipsi_qc.set_coeff_vec(copy.deepcopy(self._qc.get_coeff_vec())) return np.real(S)
def evolve(self): self._Uqite.add_circuit(self._Uprep) self._qc = qf.QuantumComputer(self._nqb) self._qc.apply_circuit(self._Uqite) if (self._do_lanczos): self._lanczos_vecs = [] self._Hlanczos_vecs = [] self._lanczos_vecs.append(copy.deepcopy(self._qc.get_coeff_vec())) qcSig_temp = qf.QuantumComputer(self._nqb) qcSig_temp.set_coeff_vec(copy.deepcopy(self._qc.get_coeff_vec())) qcSig_temp.apply_operator(self._qb_ham) self._Hlanczos_vecs.append( copy.deepcopy(qcSig_temp.get_coeff_vec())) print( f"{'beta':>7}{'E(beta)':>18}{'N(params)':>14}{'N(CNOT)':>18}{'N(measure)':>20}" ) print( '-------------------------------------------------------------------------------' ) print( f' {0.0:7.3f} {self._Ekb[0]:+15.9f} {self._n_classical_params:8} {self._n_cnot:10} {self._n_pauli_trm_measures:12}' ) if (self._print_summary_file): f = open("summary.dat", "w+", buffering=1) f.write( f"#{'beta':>7}{'E(beta)':>18}{'N(params)':>14}{'N(CNOT)':>18}{'N(measure)':>20}\n" ) f.write( '#-------------------------------------------------------------------------------\n' ) f.write( f' {0.0:7.3f} {self._Ekb[0]:+15.9f} {self._n_classical_params:8} {self._n_cnot:10} {self._n_pauli_trm_measures:12}\n' ) for kb in range(1, self._nbeta): self.do_qite_step() if (self._do_lanczos): if (kb % self._lanczos_gap == 0): self._lanczos_vecs.append( copy.deepcopy(self._qc.get_coeff_vec())) qcSig_temp = qf.QuantumComputer(self._nqb) qcSig_temp.set_coeff_vec( copy.deepcopy(self._qc.get_coeff_vec())) qcSig_temp.apply_operator(self._qb_ham) self._Hlanczos_vecs.append( copy.deepcopy(qcSig_temp.get_coeff_vec())) print( f' {kb*self._db:7.3f} {self._Ekb[kb]:+15.9f} {self._n_classical_params:8} {self._n_cnot:10} {self._n_pauli_trm_measures:12}' ) if (self._print_summary_file): f.write( f' {kb*self._db:7.3f} {self._Ekb[kb]:+15.9f} {self._n_classical_params:8} {self._n_cnot:10} {self._n_pauli_trm_measures:12}\n' ) self._Egs = self._Ekb[-1] if (self._print_summary_file): f.close()
def run(self, t = 1.0, nruns = 20, success_prob = 0.5, num_precise_bits = 4, return_phases=False): self._t = t self._nruns = nruns self._success_prob = success_prob self._num_precise_bits = num_precise_bits self._return_phases = return_phases self._Uqpe = qforte.QuantumCircuit() self._n_state_qubits = self._nqb eps = 1 - success_prob self._n_ancilla = num_precise_bits + int(np.log2(2 + (1.0/eps))) self._n_tot_qubits = self._n_state_qubits + self._n_ancilla self._abegin = self._n_state_qubits self._aend = self._n_tot_qubits - 1 self._n_classical_params = 0 self._n_pauli_trm_measures = nruns # Print options banner (should done for all algorithms). self.print_options_banner() ######### QPE ######## # add hadamard circ to split ancilla register self._Uqpe.add_circuit(self.get_Uhad()) # add initial state preparation circuit self._Uqpe.add_circuit(self._Uprep) # add controll e^-iHt circuit self._Uqpe.add_circuit(self.get_dynamics_circ()) # add reverse QFT self._Uqpe.add_circuit(self.get_qft_circuit('reverse')) computer = qforte.QuantumComputer(self._n_tot_qubits) computer.apply_circuit(self._Uqpe) self._n_cnot = self._Uqpe.get_num_cnots() if(self._fast): z_readouts = computer.measure_z_readouts_fast(self._abegin, self._aend, self._nruns) else: Zcirc = self.get_z_circuit() z_readouts = computer.measure_readouts(Zcirc, self._nruns) self._phases = [] for readout in z_readouts: val = 0.0 i = 1 for z in readout: val += z / (2**i) i += 1 self._phases.append(val) # find final binary string of phase readouts: final_readout = [] final_readout_aves = [] for i in range(self._n_ancilla): iave = 0.0 for readout in z_readouts: iave += readout[i] iave /= nruns final_readout_aves.append(iave) if (iave > (1.0/2)): final_readout.append(1) else: final_readout.append(0) self._final_phase = 0.0 counter = 0 for i, z in enumerate(final_readout): self._final_phase += z / (2**(i+1)) Eqpe = -2 * np.pi * self._final_phase / t res = stats.mode(np.asarray(self._phases)) self._mode_phase = res.mode[0] self._mode_energy = -2 * np.pi * self._mode_phase / t print('\n ==> QPE readout averages <==') print('------------------------------------------------') for i, ave in enumerate(final_readout_aves): print(' bit ', i, ': ', ave) print('\n Final bit readout: ', final_readout) ######### QPE ######## # set Egs self._Egs = Eqpe # set Umaxdepth self._Umaxdepth = self._Uqpe # Print summary banner (should done for all algorithms). self.print_summary_banner() # verify that required attributes were defined # (should done for all algorithms). self.verify_run()
def build_sa_qk_mats(self): # TODO (cleanup): imporve/update docs """Returns matrices P and Q with dimension (nstates_per_ref * len(ref_lst) X nstates_per_ref * len(ref_lst)) based on the evolution of two unitary operators Um = exp(-i * m * dt * H) and Un = exp(-i * n * dt *H). This is done for all spin adapted refrerences |Phi_K> in ref_lst, with (Q) and without (P) measuring with respect to the operator H. Elements P_mn are given by <Phi_I| Um^dag Un | Phi_J>. Elements Q_mn are given by <Phi_I| Um^dag H Un | Phi_J>. This function builds P and Q in an efficient manor and gives the same result as M built from 'matrix_element', but is unphysical for a quantum computer. Arguments --------- ref_lst : list of lists A list containing all of the spin adapted references |Phi_K> to perfrom evolutions on. Is specifically a list of lists of pairs containing coefficient vales and a lists pertaning to single determinants. As an example, ref_lst = [ [ (1.0, [1,1,0,0]) ], [ (0.7071, [0,1,1,0]), (0.7071, [1,0,0,1]) ] ]. nstates_per_ref : int The number of Krylov basis states to generate for each reference. dt_lst : list List of time steps to use for each reference (ususally the same for all references). H : QuantumOperator The operator to time evolove and measure with respect to (usually the Hamiltonain). nqubits : int The number of qubits trot_number : int The number of trotter steps (m) to perform when approximating the matrix exponentials (Um or Un). For the exponential of two non commuting terms e^(A + B), the approximate operator C(m) = (e^(A/m) * e^(B/m))^m is exact in the infinite m limit. Returns ------- s_mat : ndarray A numpy array containing the elements P_mn h_mat : ndarray A numpy array containing the elements Q_mn """ num_tot_basis = len(self._sa_ref_lst) * self._nstates_per_ref h_mat = np.zeros((num_tot_basis,num_tot_basis), dtype=complex) s_mat = np.zeros((num_tot_basis,num_tot_basis), dtype=complex) omega_lst = [] Homega_lst = [] for i, ref in enumerate(self._sa_ref_lst): for m in range(self._nstates_per_ref): # TODO (cleanup): will need to consider gate count for this part. Um = qforte.QuantumCircuit() phase1 = 1.0 if(m>0): fact = (0.0-1.0j) * m * self._mr_dt expn_op1, phase1 = trotterize(self._qb_ham, factor=fact, trotter_number=self._trotter_number) Um.add_circuit(expn_op1) QC = qforte.QuantumComputer(self._nqb) state_prep_lst = [] for term in ref: coeff = term[0] det = term[1] idx = ref_to_basis_idx(det) state = qforte.QuantumBasis(idx) state_prep_lst.append( (state, coeff) ) QC.set_state(state_prep_lst) QC.apply_circuit(Um) QC.apply_constant(phase1) omega_lst.append(np.asarray(QC.get_coeff_vec(), dtype=complex)) QC.apply_operator(self._qb_ham) Homega_lst.append(np.asarray(QC.get_coeff_vec(), dtype=complex)) if(self._diagonalize_each_step): print('\n\n') print(f"{'k(S)':>7}{'E(Npar)':>19}{'N(params)':>14}{'N(CNOT)':>18}{'N(measure)':>20}") print('-------------------------------------------------------------------------------') # TODO (opt): add this to previous loop for p in range(num_tot_basis): for q in range(p, num_tot_basis): h_mat[p][q] = np.vdot(omega_lst[p], Homega_lst[q]) h_mat[q][p] = np.conj(h_mat[p][q]) s_mat[p][q] = np.vdot(omega_lst[p], omega_lst[q]) s_mat[q][p] = np.conj(s_mat[p][q]) if (self._diagonalize_each_step): # TODO (cleanup): have this print to a separate file evals, evecs = canonical_geig_solve(s_mat[0:p+1, 0:p+1], h_mat[0:p+1, 0:p+1], print_mats=False, sort_ret_vals=True) scond = np.linalg.cond(s_mat[0:p+1, 0:p+1]) cs_str = '{:.2e}'.format(scond) k = p+1 self._n_classical_params = k if(k==1): self._n_cnot = self._srqk._n_cnot else: self._n_cnot = 2 * Um.get_num_cnots() self._n_pauli_trm_measures = k * self._Nl + self._srqk._n_pauli_trm_measures self._n_pauli_trm_measures += k * (k-1) * self._Nl self._n_pauli_trm_measures += k * (k-1) print(f' {scond:7.2e} {np.real(evals[self._target_root]):+15.9f} {self._n_classical_params:8} {self._n_cnot:10} {self._n_pauli_trm_measures:12}') return s_mat, h_mat
def get_residual_vector(self, trial_amps): if (self._pool_type == 'sa_SD'): raise ValueError( 'Must use single term particle-hole nbody operators for residual calculation' ) temp_pool = qforte.SQOpPool() for param, top in zip(trial_amps, self._tops): temp_pool.add_term(param, self._pool[top][1]) A = temp_pool.get_quantum_operator('commuting_grp_lex') U, U_phase = trotterize(A, trotter_number=self._trotter_number) if U_phase != 1.0 + 0.0j: raise ValueError( "Encountered phase change, phase not equal to (1.0 + 0.0i)") qc_res = qforte.QuantumComputer(self._nqb) qc_res.apply_circuit(self._Uprep) qc_res.apply_circuit(U) qc_res.apply_operator(self._qb_ham) qc_res.apply_circuit(U.adjoint()) coeffs = qc_res.get_coeff_vec() residuals = [] for m in self._tops: # 1. Identify the excitation operator sq_op = self._pool[m][1] # occ => i,j,k,... # vir => a,b,c,... # sq_op is 1.0(a^ b^ i j) - 1.0(j^ i^ b a) temp_idx = sq_op.terms()[0][1][-1] # TODO: This code assumes that the first N orbitals are occupied, and the others are virtual. # Use some other mechanism to identify the occupied orbitals, so we can use use PQE on excited # determinants. if temp_idx < int( sum(self._ref) / 2): # if temp_idx is an occupied idx sq_ex_op = sq_op.terms()[0][1] else: sq_ex_op = sq_op.terms()[1][1] # 2. Get the bit representation of the sq_ex_op acting on the reference. # We determine the projective condition for this amplitude by zero'ing this residual. nbody = int(len(sq_ex_op) / 2) # `destroyed` exists solely for error catching. destroyed = False excited_det = qforte.QuantumBasis(self._nqb) for k, occ in enumerate(self._ref): excited_det.set_bit(k, occ) # loop over annihilators for p in reversed(range(nbody, 2 * nbody)): if (excited_det.get_bit(sq_ex_op[p]) == 0): destroyed = True break excited_det.set_bit(sq_ex_op[p], 0) # then over creators for p in reversed(range(0, nbody)): if (excited_det.get_bit(sq_ex_op[p]) == 1): destroyed = True break excited_det.set_bit(sq_ex_op[p], 1) if destroyed: raise ValueError( "no ops should destroy reference, something went wrong!!") I = excited_det.add() # 3. Compute the phase of the operator, relative to its determinant. qc_temp = qforte.QuantumComputer(self._nqb) qc_temp.apply_circuit(self._Uprep) qc_temp.apply_operator(sq_op.jw_transform()) phase_factor = qc_temp.get_coeff_vec()[I] # 4. Get the residual element, after accounting for numerical noise. res_m = coeffs[I] * phase_factor if (np.imag(res_m) != 0.0): raise ValueError( "residual has imaginary component, something went wrong!!") if (self._noise_factor > 1e-12): res_m = np.random.normal(np.real(res_m), self._noise_factor) residuals.append(res_m) return residuals
def update_ansatz(self): self._n_pauli_measures_k = 0 x0 = copy.deepcopy(self._tamps) init_gues_energy = self.energy_feval(x0) # do U^dag e^iH U |Phi_o> = |Phi_res> temp_pool = qf.SQOpPool() for param, top in zip(self._tamps, self._tops): temp_pool.add_term(param, self._pool[top][1]) A = temp_pool.get_quantum_operator('commuting_grp_lex') U, U_phase = trotterize(A, trotter_number=self._trotter_number) if U_phase != 1.0 + 0.0j: raise ValueError( "Encountered phase change, phase not equal to (1.0 + 0.0i)") qc_res = qf.QuantumComputer(self._nqb) qc_res.apply_circuit(self._Uprep) qc_res.apply_circuit(U) qc_res.apply_circuit(self._eiH) qc_res.apply_circuit(U.adjoint()) res_coeffs = qc_res.get_coeff_vec() lgrst_op_factor = 0.0 # ned to sort the coeffs to psi_tilde temp_order_resids = [] # build different res_sq list using M_omega if (self._M_omega != 'inf'): res_sq_tmp = [ np.real(np.conj(res_coeffs[I]) * res_coeffs[I]) for I in range(len(res_coeffs)) ] # Nmu_lst => [ det1, det2, det3, ... det_M_omega] det_lst = np.random.choice(len(res_coeffs), self._M_omega, p=res_sq_tmp) print(f'|Co|dt^2 : {np.amax(res_sq_tmp):12.14f}') print( f'mu_o : {np.where(res_sq_tmp == np.amax(res_sq_tmp))[0][0]}' ) No_idx = np.where(res_sq_tmp == np.amax(res_sq_tmp))[0][0] print(f'\nNo_idx {No_idx:4}') No = np.count_nonzero(det_lst == No_idx) print(f'\nNo {No:10}') res_sq = [] Nmu_lst = [] for mu in range(len(res_coeffs)): Nmu = np.count_nonzero(det_lst == mu) if (Nmu > 0): print( f'mu: {mu:8} Nmu {Nmu:10} r_mu: { Nmu / (self._M_omega):12.14f} ' ) Nmu_lst.append((Nmu, mu)) res_sq.append((Nmu / (self._M_omega), mu)) ## 1. sort Nmu_lst.sort() res_sq.sort() ## 2. set norm self._curr_res_sq_norm = 0.0 for rmu_sq in res_sq[:-1]: self._curr_res_sq_norm += rmu_sq[0] self._curr_res_sq_norm /= (self._dt * self._dt) ## 3. print stuff print(' \n--> Begin selection opt with residual magnitudes:') print(' Initial guess energy: ', round(init_gues_energy, 10)) print( f' Norm of approximate res vec: {np.sqrt(self._curr_res_sq_norm):14.12f}' ) ## 4. check conv status (need up update function with if(M_omega != 'inf')) if (len(Nmu_lst) == 1): print(' SPQE converged with M_omega thresh!') self._converged = 1 self._final_energy = self._energies[-1] self._final_result = self._results[-1] else: self._converged = 0 ## 5. add new toperator if not self._converged: if self._verbose: print('\n') print(' op index (Imu) Number of times measured') print(' -----------------------------------------------') res_sq_sum = 0.0 n_ops_added = 0 for Nmu_tup in Nmu_lst[:-1]: if (self._verbose): print( f" {Nmu_tup[1]:10} {np.real(Nmu_tup[0]):14}" ) n_ops_added += 1 if (Nmu_tup[1] not in self._tops): self._tops.insert(0, Nmu_tup[1]) self._tamps.insert(0, 0.0) self.add_op_from_basis_idx(Nmu_tup[1]) self._n_classical_params_lst.append(len(self._tops)) else: # when M_omega == 'inf', proceed with standard SPQE res_sq = [(np.real(np.conj(res_coeffs[I]) * res_coeffs[I]), I) for I in range(len(res_coeffs))] res_sq.sort() self._curr_res_sq_norm = 0.0 for rmu_sq in res_sq[:-1]: self._curr_res_sq_norm += rmu_sq[0] self._curr_res_sq_norm /= (self._dt * self._dt) print( ' \n--> Begin selection opt with residual magnitudes |r_mu|:') print(' Initial guess energy: ', round(init_gues_energy, 10)) print( f' Norm of res vec: {np.sqrt(self._curr_res_sq_norm):14.12f}' ) self.conv_status() if not self._converged: if self._verbose: print('\n') print(' op index (Imu) Residual Facotr') print(' -----------------------------------------------') res_sq_sum = 0.0 n_ops_added = 0 # for the canonical SPQE batch addition from |r^2| as a importance criterion if (self._use_cumulative_thresh): temp_ops = [] for rmu_sq in res_sq[:-1]: res_sq_sum += (rmu_sq[0] / (self._dt * self._dt)) if res_sq_sum > (self._spqe_thresh * self._spqe_thresh): if (self._verbose): Ktemp = self.get_op_from_basis_idx(rmu_sq[1]) print( f" {rmu_sq[1]:10} {np.real(rmu_sq[0])/(self._dt * self._dt):14.12f} {Ktemp.str()}" ) n_ops_added += 1 if (rmu_sq[1] not in self._tops): temp_ops.append(rmu_sq[1]) self.add_op_from_basis_idx(rmu_sq[1]) ### consistant with op ordering inspired by traditional renormalization approaches ### for temp_op in temp_ops[::-1]: self._tops.insert(0, temp_op) self._tamps.insert(0, 0.0) else: # add individual ops res_sq.reverse() op_added = False for rmu_sq in res_sq[1:]: if (op_added): break print( f" {rmu_sq[1]:10} {np.real(rmu_sq[0])/(self._dt * self._dt):14.12f}" ) if (rmu_sq[1] not in self._tops): print('op added!') self._tops.insert(0, rmu_sq[1]) self._tamps.insert(0, 0.0) self.add_op_from_basis_idx(rmu_sq[1]) op_added = True self._n_classical_params_lst.append(len(self._tops))
def build_qk_mats(self): """Returns matrices S and H needed for the MRSQK algorithm using the Trotterized form of the unitary operators U_n = exp(-i n dt H) The mathematical operations of this function are unphysical for a quantum computer, but efficient for a simulator. Returns ------- s_mat : ndarray A numpy array containing the elements S_mn = <Phi | Um^dag Un | Phi>. _nstates by _nstates h_mat : ndarray A numpy array containing the elements H_mn = <Phi | Um^dag H Un | Phi> _nstates by _nstates """ h_mat = np.zeros((self._nstates, self._nstates), dtype=complex) s_mat = np.zeros((self._nstates, self._nstates), dtype=complex) # Store these vectors for the aid of MRSQK self._omega_lst = [] Homega_lst = [] if (self._diagonalize_each_step): print('\n\n') print( f"{'k(S)':>7}{'E(Npar)':>19}{'N(params)':>14}{'N(CNOT)':>18}{'N(measure)':>20}" ) print( '-------------------------------------------------------------------------------' ) if (self._print_summary_file): f = open("summary.dat", "w+", buffering=1) f.write( f"#{'k(S)':>7}{'E(Npar)':>19}{'N(params)':>14}{'N(CNOT)':>18}{'N(measure)':>20}\n" ) f.write( '#-------------------------------------------------------------------------------\n' ) for m in range(self._nstates): # Compute U_m = exp(-i m dt H) Um = qforte.QuantumCircuit() Um.add_circuit(self._Uprep) phase1 = 1.0 if (m > 0): fact = (0.0 - 1.0j) * m * self._dt expn_op1, phase1 = trotterize( self._qb_ham, factor=fact, trotter_number=self._trotter_number) Um.add_circuit(expn_op1) # Compute U_m |φ> QC = qforte.QuantumComputer(self._nqb) QC.apply_circuit(Um) QC.apply_constant(phase1) self._omega_lst.append( np.asarray(QC.get_coeff_vec(), dtype=complex)) # Compute H U_m |φ> QC.apply_operator(self._qb_ham) Homega_lst.append(np.asarray(QC.get_coeff_vec(), dtype=complex)) # Compute S_mn = <φ| U_m^\dagger U_n |φ> and H_mn = <φ| U_m^\dagger H U_n |φ> for n in range(len(self._omega_lst)): h_mat[m][n] = np.vdot(self._omega_lst[m], Homega_lst[n]) h_mat[n][m] = np.conj(h_mat[m][n]) s_mat[m][n] = np.vdot(self._omega_lst[m], self._omega_lst[n]) s_mat[n][m] = np.conj(s_mat[m][n]) if (self._diagonalize_each_step): # TODO (cleanup): have this print to a separate file k = m + 1 evals, evecs = canonical_geig_solve(s_mat[0:k, 0:k], h_mat[0:k, 0:k], print_mats=False, sort_ret_vals=True) scond = np.linalg.cond(s_mat[0:k, 0:k]) self._n_classical_params = k self._n_cnot = 2 * Um.get_num_cnots() self._n_pauli_trm_measures = k * self._Nl self._n_pauli_trm_measures += k * (k - 1) * self._Nl self._n_pauli_trm_measures += k * (k - 1) print( f' {scond:7.2e} {np.real(evals[self._target_root]):+15.9f} {self._n_classical_params:8} {self._n_cnot:10} {self._n_pauli_trm_measures:12}' ) if (self._print_summary_file): f.write( f' {scond:7.2e} {np.real(evals[self._target_root]):+15.9f} {self._n_classical_params:8} {self._n_cnot:10} {self._n_pauli_trm_measures:12}\n' ) if (self._diagonalize_each_step and self._print_summary_file): f.close() self._n_classical_params = self._nstates self._n_cnot = 2 * Um.get_num_cnots() # diagonal terms of Hbar self._n_pauli_trm_measures = self._nstates * self._Nl # off-diagonal of Hbar (<X> and <Y> of Hadamard test) self._n_pauli_trm_measures += self._nstates * (self._nstates - 1) * self._Nl # off-diagonal of S (<X> and <Y> of Hadamard test) self._n_pauli_trm_measures += self._nstates * (self._nstates - 1) return s_mat, h_mat