Exemple #1
0
    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]
Exemple #2
0
    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')
Exemple #3
0
 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()
Exemple #4
0
    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())
Exemple #5
0
    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
        ]
Exemple #6
0
    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
Exemple #7
0
    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()
Exemple #8
0
    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
Exemple #9
0
    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))