def fill_pool(self): """ This function populates an operator pool with SQOperator objects. """ if self._pool_type in {'sa_SD', 'GSD', 'SD', 'SDT', 'SDTQ', 'SDTQP', 'SDTQPH'}: self._pool_obj = qf.SQOpPool() self._pool_obj.set_orb_spaces(self._ref) self._pool_obj.fill_pool(self._pool_type) elif isinstance(self._pool_type, qf.SQOpPool): self._pool_obj = self._pool_type else: raise ValueError('Invalid operator pool type specified.') # If possible, impose symmetry restriction to operator pool # Currently, symmetry is supported for system_type='molecule' and build_type='psi4' if hasattr(self._sys, 'point_group'): temp_sq_pool = qf.SQOpPool() for sq_operator in self._pool_obj.terms(): create = sq_operator[1].terms()[0][1] annihilate = sq_operator[1].terms()[0][2] if sq_op_find_symmetry(self._sys.orb_irreps_to_int, create, annihilate) == self._irrep: temp_sq_pool.add(sq_operator[0], sq_operator[1]) self._pool_obj = temp_sq_pool self._Nm = [len(operator.jw_transform().terms()) for _, operator in self._pool_obj]
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._curr_energy = 0 self._Nm = [] self._tamps = [] self._tops = [] self._pool_obj = qf.SQOpPool() kwargs.setdefault('irrep', None) if hasattr(self._sys, 'point_group'): irreps = list(range(len(self._sys.point_group[1]))) if kwargs['irrep'] is None: print('\nWARNING: The {0} point group was detected, but no irreducible representation was specified.\n' ' Proceeding with totally symmetric.\n'.format(self._sys.point_group[0].capitalize())) self._irrep = 0 elif kwargs['irrep'] in irreps: self._irrep = kwargs['irrep'] else: raise ValueError("{0} is not an irreducible representation of {1}.\n" " Choose one of {2} corresponding to the\n" " {3} irreducible representations of {1}".format(kwargs['irrep'], self._sys.point_group[0].capitalize(), irreps, self._sys.point_group[1])) elif kwargs['irrep'] is not None: print('\nWARNING: Point group information not found.\n' ' Ignoring "irrep" and proceeding without symmetry.\n')
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._curr_energy = 0 self._Nm = [] self._tamps = [] self._tops = [] self._pool = [] self._pool_obj = qf.SQOpPool()
def build_expansion_pool(self): print('\n==> Building expansion pool <==') self._sig = qf.QuantumOpPool() if (self._expansion_type == 'complete_qubit'): if (self._nqb > 6): raise ValueError( 'Using complete qubits expansion will result in a very large number of terms!' ) self._sig.fill_pool("complete_qubit", self._ref) elif (self._expansion_type == 'cqoy'): self._sig.fill_pool("cqoy", self._ref) elif (self._expansion_type in {'SD', 'GSD', 'SDT', 'SDTQ', 'SDTQP', 'SDTQPH'}): P = qf.SQOpPool() P.set_orb_spaces(self._ref) P.fill_pool(self._expansion_type) sig_temp = P.get_quantum_operator("commuting_grp_lex", False) # Filter the generated operators, so that only those with an odd number of Y gates are allowed. # See section "Real Hamiltonians and states" in the SI of Motta for theoretical justification. # Briefly, this method solves Ax=b, but all b elements with an odd number of Y gates are imaginary and # thus vanish. This method will not be correct for non-real Hamiltonians or states. for alph, rho in sig_temp.terms(): nygates = 0 temp_rho = qf.QuantumCircuit() for gate in rho.gates(): temp_rho.add_gate( qf.gate(gate.gate_id(), gate.target(), gate.control())) if (gate.gate_id() == "Y"): nygates += 1 if (nygates % 2 == 1): rho_op = qf.QuantumOperator() rho_op.add_term(1.0, temp_rho) self._sig.add_term(1.0, rho_op) elif (self._expansion_type == 'test'): self._sig.fill_pool("test", self._ref) else: raise ValueError('Invalid expansion type specified.') self._NI = len(self._sig.terms())
def fill_pool(self): self._pool_obj = qf.SQOpPool() self._pool_obj.set_orb_spaces(self._ref) if self._pool_type in { 'sa_SD', 'GSD', 'SD', 'SDT', 'SDTQ', 'SDTQP', 'SDTQPH' }: self._pool_obj.fill_pool(self._pool_type) else: raise ValueError('Invalid operator pool type specified.') self._pool = self._pool_obj.terms() self._Nm = [ len(operator.jw_transform().terms()) for _, operator in self._pool ]
def ansatz_circuit(self, amplitudes=None): """ This function returns the Circuit object built from the appropriate amplitudes. Parameters ---------- amplitudes : list A list of parameters that define the variational degrees of freedom in the state preparation circuit Uvqc. This is needed for the scipy minimizer. """ temp_pool = qf.SQOpPool() tamps = self._tamps if amplitudes is None else amplitudes for tamp, top in zip(tamps, self._tops): temp_pool.add(tamp, self._pool_obj[top][1]) A = temp_pool.get_qubit_operator('commuting_grp_lex') U, phase1 = trotterize(A, trotter_number=self._trotter_number) if phase1 != 1.0 + 0.0j: raise ValueError("Encountered phase change, phase not equal to (1.0 + 0.0i)") return U
def run(self, spqe_thresh=1.0e-2, spqe_maxiter=20, dt=0.001, M_omega='inf', opt_thresh=1.0e-5, opt_maxiter=30, use_cumulative_thresh=True): if (self._state_prep_type != 'occupation_list'): raise ValueError( "SPQE implementation can only handle occupation_list Hartree-Fock reference." ) self._spqe_thresh = spqe_thresh self._spqe_maxiter = spqe_maxiter self._dt = dt if (M_omega != 'inf'): self._M_omega = int(M_omega) else: self._M_omega = M_omega self._use_cumulative_thresh = use_cumulative_thresh self._opt_thresh = opt_thresh self._opt_maxiter = opt_maxiter self._nbody_counts = [] self._n_classical_params_lst = [] self._results = [] self._energies = [] self._grad_norms = [] self._tops = [] self._tamps = [] self._converged = False self._res_vec_evals = 0 self._res_m_evals = 0 self._curr_energy = 0.0 self._n_classical_params = 0 self._n_cnot = 0 self._n_cnot_lst = [] self._n_pauli_trm_measures = 0 self._n_pauli_trm_measures_lst = [] self.print_options_banner() self._Nm = [] self._pool_type = 'full' self._eiH, self._eiH_phase = trotterize( self._qb_ham, factor=self._dt * (0.0 + 1.0j), trotter_number=self._trotter_number) for occupation in self._ref: if occupation: self._nbody_counts.append(0) self._pool_obj = qf.SQOpPool() for I in range(2**self._nqb): self._pool_obj.add_term(0.0, self.get_op_from_basis_idx(I)) self.build_orb_energies() spqe_iter = 0 hit_maxiter = 0 if (self._print_summary_file): f = open("summary.dat", "w+", buffering=1) f.write( f"#{'Iter(k)':>8}{'E(k)':>14}{'N(params)':>17}{'N(CNOT)':>18}{'N(measure)':>20}\n" ) f.write( '#-------------------------------------------------------------------------------\n' ) while not self._converged: print('\n\n -----> SPQE iteration ', spqe_iter, ' <-----\n') self.update_ansatz() if self._converged: break if (self._verbose): print('\ntoperators included from pool: \n', self._tops) print('\ntamplitudes for tops: \n', self._tamps) self.solve() if (self._verbose): print('\ntamplitudes for tops post solve: \n', np.real(self._tamps)) if (self._print_summary_file): f.write( f' {spqe_iter:7} {self._energies[-1]:+15.9f} {len(self._tamps):8} {self._n_cnot_lst[-1]:10} {sum(self._n_pauli_trm_measures_lst):12}\n' ) spqe_iter += 1 if spqe_iter > self._spqe_maxiter - 1: hit_maxiter = 1 break if (self._print_summary_file): f.close() if hit_maxiter: self._Egs = self.get_final_energy(hit_max_spqe_iter=1) self._Egs = self.get_final_energy() print("\n\n") print("---> Final n-body excitation counts in SPQE ansatz <---") print("\n") print(f"{'Excitaion order':>20}{'Number of operators':>30}") print('---------------------------------------------------------') for l, nl in enumerate(self._nbody_counts): print(f"{l+1:12} {nl:14}") print('\n\n') print( f"{'Iter(k)':>8}{'E(k)':>14}{'N(params)':>17}{'N(CNOT)':>18}{'N(measure)':>20}" ) print( '-------------------------------------------------------------------------------' ) for k, Ek in enumerate(self._energies): print( f' {k:7} {Ek:+15.9f} {self._n_classical_params_lst[k]:8} {self._n_cnot_lst[k]:10} {sum(self._n_pauli_trm_measures_lst[:k+1]):12}' ) self._n_classical_params = len(self._tamps) self._n_cnot = self._n_cnot_lst[-1] self._n_pauli_trm_measures = sum(self._n_pauli_trm_measures_lst) self.print_summary_banner() self.verify_run()
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))