Ejemplo n.º 1
0
def test_vbc_svd_decomps():
    molecule = build_h4square_moleculardata()
    oei, tei = molecule.get_integrals()
    nele = 4
    nalpha = 2
    nbeta = 2
    sz = 0
    norbs = oei.shape[0]
    nso = 2 * norbs
    fqe_wf = fqe.Wavefunction([[nele, sz, norbs]])
    fqe_wf.set_wfn(strategy='random')
    fqe_wf.normalize()
    _, tpdm = fqe_wf.sector((nele, sz)).get_openfermion_rdms()
    d3 = fqe_wf.sector((nele, sz)).get_three_pdm()

    adapt = VBC(oei, tei, nalpha, nbeta, iter_max=50)
    acse_residual = two_rdo_commutator_symm(adapt.reduced_ham.two_body_tensor,
                                            tpdm, d3)
    new_residual = np.zeros_like(acse_residual)
    for p, q, r, s in product(range(nso), repeat=4):
        new_residual[p, q, r, s] = (acse_residual[p, q, r, s] -
                                    acse_residual[s, r, q, p]) / 2

    sos_op = adapt.get_svd_tensor_decomp(new_residual, None)

    # reconstruct tensor from sop
    test_tensor = np.zeros_like(new_residual)
    for v, cc in zip(sos_op.basis_rotation, sos_op.charge_charge):
        vc = v.conj()
        test_tensor += np.einsum('pi,si,ij,qj,rj->pqrs', v, vc, -1j * cc, v,
                                 vc)
    assert np.allclose(test_tensor, new_residual)
Ejemplo n.º 2
0
def test_vbc_time_evolve():
    molecule = build_h4square_moleculardata()
    oei, tei = molecule.get_integrals()
    nele = molecule.n_electrons
    nalpha = nele // 2
    nbeta = nele // 2
    sz = 0
    norbs = oei.shape[0]
    nso = 2 * norbs
    fqe_wf = fqe.Wavefunction([[nele, sz, norbs]])
    fqe_wf.set_wfn(strategy='random')
    fqe_wf.normalize()
    nfqe_wf = fqe.get_number_conserving_wavefunction(nele, norbs)
    nfqe_wf.sector((nele, sz)).coeff = fqe_wf.sector((nele, sz)).coeff
    _, tpdm = nfqe_wf.sector((nele, sz)).get_openfermion_rdms()
    d3 = nfqe_wf.sector((nele, sz)).get_three_pdm()

    adapt = VBC(oei, tei, nalpha, nbeta, iter_max=50)
    acse_residual = two_rdo_commutator_symm(adapt.reduced_ham.two_body_tensor,
                                            tpdm, d3)
    sos_op = adapt.get_takagi_tensor_decomp(acse_residual, None)

    test_wf = copy.deepcopy(nfqe_wf)
    test_wf = sos_op.time_evolve(test_wf)

    true_wf = copy.deepcopy(nfqe_wf)
    for v, cc in zip(sos_op.basis_rotation, sos_op.charge_charge):
        vc = v.conj()
        new_tensor = np.einsum('pi,si,ij,qj,rj->pqrs', v, vc, -1j * cc, v, vc)
        if np.isclose(np.linalg.norm(new_tensor), 0):
            continue
        fop = of.FermionOperator()
        for p, q, r, s in product(range(nso), repeat=4):
            op = ((p, 1), (s, 0), (q, 1), (r, 0))
            fop += of.FermionOperator(op, coefficient=new_tensor[p, q, r, s])
        fqe_op = build_hamiltonian(1j * fop, conserve_number=True)
        true_wf = true_wf.time_evolve(1, fqe_op)
    true_wf = evolve_fqe_givens_unrestricted(true_wf, sos_op.one_body_rotation)

    assert np.isclose(abs(fqe.vdot(true_wf, test_wf))**2, 1)
Ejemplo n.º 3
0
    def adapt_vqe(self,
                  initial_wf: Wavefunction,
                  opt_method: str = 'L-BFGS-B',
                  opt_options=None,
                  v_reconstruct: bool = True,
                  num_ops_add: int = 1):
        """
        Run ADAPT-VQE using

        Args:
            initial_wf: Initial wavefunction at the start of the calculation
            opt_method: scipy optimizer to use
            opt_options: options  for scipy optimizer
            v_reconstruct: use valdemoro reconstruction
            num_ops_add: add this many operators from the pool to the
                         wavefunction
        """
        if opt_options is None:
            opt_options = {}
        operator_pool = []
        operator_pool_fqe: List[ABCHamiltonian] = []
        existing_parameters: List[float] = []
        self.gradients = []
        self.energies = [initial_wf.expectationValue(self.k2_fop)]
        iteration = 0
        while iteration < self.iter_max:
            # get current wavefunction
            wf = copy.deepcopy(initial_wf)
            for fqe_op, coeff in zip(operator_pool_fqe, existing_parameters):
                wf = wf.time_evolve(coeff, fqe_op)

            # calculate rdms for grad
            _, tpdm = wf.sector((self.nele, self.sz)).get_openfermion_rdms()
            if v_reconstruct:
                d3 = 6 * valdemaro_reconstruction(tpdm / 2, self.nele)
            else:
                d3 = wf.sector((self.nele, self.sz)).get_three_pdm()

            # get ACSE Residual and 2-RDM gradient
            acse_residual = two_rdo_commutator_symm(
                self.reduced_ham.two_body_tensor, tpdm, d3)
            one_body_residual = one_rdo_commutator_symm(
                self.reduced_ham.two_body_tensor, tpdm)

            # calculate grad of each operator in the pool
            pool_grad = []
            for operator in self.operator_pool.op_pool:
                grad_val = 0
                for op_term, coeff in operator.terms.items():
                    idx = [xx[0] for xx in op_term]
                    if len(idx) == 4:
                        grad_val += acse_residual[tuple(idx)] * coeff
                    elif len(idx) == 2:
                        grad_val += one_body_residual[tuple(idx)] * coeff
                pool_grad.append(grad_val)

            max_grad_terms_idx = \
                np.argsort(np.abs(pool_grad))[::-1][:num_ops_add]

            pool_terms = [
                self.operator_pool.op_pool[i] for i in max_grad_terms_idx
            ]
            operator_pool.extend(pool_terms)
            fqe_ops: List[ABCHamiltonian] = []
            for f_op in pool_terms:
                fqe_ops.append(
                    build_hamiltonian(1j * f_op,
                                      self.sdim,
                                      conserve_number=True))
            operator_pool_fqe.extend(fqe_ops)
            existing_parameters.extend([0] * len(fqe_ops))

            new_parameters, current_e = self.optimize_param(
                operator_pool_fqe,
                existing_parameters,
                initial_wf,
                opt_method,
                opt_options=opt_options)

            existing_parameters = new_parameters.tolist()
            if self.verbose:
                print(iteration, current_e, max(np.abs(pool_grad)))
            self.energies.append(current_e)
            self.gradients.append(pool_grad)
            if max(np.abs(pool_grad)) < self.stopping_eps or np.abs(
                    self.energies[-2] - self.energies[-1]) < self.delta_e_eps:
                break
            iteration += 1
Ejemplo n.º 4
0
def test_generalized_doubles_takagi():
    molecule = build_lih_moleculardata()
    oei, tei = molecule.get_integrals()
    nele = 4
    nalpha = 2
    nbeta = 2
    sz = 0
    norbs = oei.shape[0]
    nso = 2 * norbs
    fqe_wf = fqe.Wavefunction([[nele, sz, norbs]])
    fqe_wf.set_wfn(strategy='hartree-fock')
    fqe_wf.normalize()
    _, tpdm = fqe_wf.sector((nele, sz)).get_openfermion_rdms()
    d3 = fqe_wf.sector((nele, sz)).get_three_pdm()

    soei, stei = spinorb_from_spatial(oei, tei)
    astei = np.einsum('ijkl', stei) - np.einsum('ijlk', stei)
    molecular_hamiltonian = of.InteractionOperator(0, soei, 0.25 * astei)
    reduced_ham = make_reduced_hamiltonian(molecular_hamiltonian,
                                           nalpha + nbeta)
    acse_residual = two_rdo_commutator_symm(reduced_ham.two_body_tensor, tpdm,
                                            d3)
    for p, q, r, s in product(range(nso), repeat=4):
        if p == q or r == s:
            continue
        assert np.isclose(acse_residual[p, q, r, s],
                          -acse_residual[s, r, q, p].conj())

    Zlp, Zlm, _, one_body_residual = doubles_factorization_takagi(acse_residual)
    test_fop = get_fermion_op(one_body_residual)
    # test the first four factors
    for ll in range(4):
        test_fop += 0.25 * get_fermion_op(Zlp[ll])**2
        test_fop += 0.25 * get_fermion_op(Zlm[ll])**2

        op1mat = Zlp[ll]
        op2mat = Zlm[ll]
        w1, v1 = sp.linalg.schur(op1mat)
        w1 = np.diagonal(w1)
        assert np.allclose(v1 @ np.diag(w1) @ v1.conj().T, op1mat)

        v1c = v1.conj()
        w2, v2 = sp.linalg.schur(op2mat)
        w2 = np.diagonal(w2)
        assert np.allclose(v2 @ np.diag(w2) @ v2.conj().T, op2mat)
        oww1 = np.outer(w1, w1)

        fqe_wf = fqe.Wavefunction([[nele, sz, norbs]])
        fqe_wf.set_wfn(strategy='hartree-fock')
        fqe_wf.normalize()
        nfqe_wf = fqe.get_number_conserving_wavefunction(nele, norbs)
        nfqe_wf.sector((nele, sz)).coeff = fqe_wf.sector((nele, sz)).coeff

        this_generatory = np.einsum('pi,si,ij,qj,rj->pqrs', v1, v1c, oww1, v1,
                                    v1c)
        fop = of.FermionOperator()
        for p, q, r, s in product(range(nso), repeat=4):
            op = ((p, 1), (s, 0), (q, 1), (r, 0))
            fop += of.FermionOperator(op,
                                      coefficient=this_generatory[p, q, r, s])

        fqe_fop = build_hamiltonian(1j * fop, norb=norbs, conserve_number=True)
        exact_wf = fqe.apply_generated_unitary(nfqe_wf, 1, 'taylor', fqe_fop)

        test_wf = fqe.algorithm.low_rank.evolve_fqe_givens_unrestricted(
            nfqe_wf,
            v1.conj().T)
        test_wf = fqe.algorithm.low_rank.evolve_fqe_charge_charge_unrestricted(
            test_wf, -oww1.imag)
        test_wf = fqe.algorithm.low_rank.evolve_fqe_givens_unrestricted(
            test_wf, v1)

        assert np.isclose(abs(fqe.vdot(test_wf, exact_wf))**2, 1)
Ejemplo n.º 5
0
    def vbc(self,
            initial_wf: Wavefunction,
            opt_method: str = 'L-BFGS-B',
            opt_options=None,
            num_opt_var=None,
            v_reconstruct=False,
            generator_decomp=None,
            generator_rank=None):
        """The variational Brillouin condition method

        Solve for the 2-body residual and then variationally determine
        the step size.  This exact simulation cannot be implemented without
        Trotterization.  A proxy for the approximate evolution is the update_
        rank pameter which limites the rank of the residual.

        Args:
            initial_wf: initial wavefunction
            opt_method: scipy optimizer name
            num_opt_var: Number of optimization variables to consider
            v_reconstruct: use valdemoro reconstruction of 3-RDM to calculate
                           the residual
            generator_decomp: None, takagi, or svd
            generator_rank: number of generator terms to take
        """
        if opt_options is None:
            opt_options = {}
        self.num_opt_var = num_opt_var
        nso = 2 * self.sdim
        operator_pool: List[Union[ABCHamiltonian, SumOfSquaresOperator]] = []
        operator_pool_fqe: List[
            Union[ABCHamiltonian, SumOfSquaresOperator]] = []
        existing_parameters: List[float] = []
        self.energies = []
        self.energies = [initial_wf.expectationValue(self.k2_fop)]
        self.residuals = []
        iteration = 0
        while iteration < self.iter_max:
            # get current wavefunction
            wf = copy.deepcopy(initial_wf)
            for op, coeff in zip(operator_pool_fqe, existing_parameters):
                if np.isclose(coeff, 0):
                    continue
                if isinstance(op, ABCHamiltonian):
                    wf = wf.time_evolve(coeff, op)
                elif isinstance(op, SumOfSquaresOperator):
                    for v, cc in zip(op.basis_rotation, op.charge_charge):
                        wf = evolve_fqe_givens_unrestricted(wf, v.conj().T)
                        wf = evolve_fqe_charge_charge_unrestricted(
                            wf, coeff * cc)
                        wf = evolve_fqe_givens_unrestricted(wf, v)
                    wf = evolve_fqe_givens_unrestricted(wf,
                                                        op.one_body_rotation)
                else:
                    raise ValueError("Can't evolve operator type {}".format(
                        type(op)))

            # calculate rdms for grad
            _, tpdm = wf.sector((self.nele, self.sz)).get_openfermion_rdms()
            if v_reconstruct:
                d3 = 6 * valdemaro_reconstruction_functional(
                    tpdm / 2, self.nele)
            else:
                d3 = wf.sector((self.nele, self.sz)).get_three_pdm()

            # get ACSE Residual and 2-RDM gradient
            acse_residual = two_rdo_commutator_symm(
                self.reduced_ham.two_body_tensor, tpdm, d3)

            if generator_decomp is None:
                fop = get_fermion_op(acse_residual)
            elif generator_decomp is 'svd':
                new_residual = np.zeros_like(acse_residual)
                for p, q, r, s in product(range(nso), repeat=4):
                    new_residual[p, q, r, s] = (acse_residual[p, q, r, s] -
                                                acse_residual[s, r, q, p]) / 2

                fop = self.get_svd_tensor_decomp(new_residual, generator_rank)
            elif generator_decomp is 'takagi':
                fop = self.get_takagi_tensor_decomp(acse_residual,
                                                    generator_rank)
            else:
                raise ValueError(
                    "Generator decomp must be None, svd, or takagi")

            operator_pool.extend([fop])
            fqe_ops: List[Union[ABCHamiltonian, SumOfSquaresOperator]] = []
            if isinstance(fop, ABCHamiltonian):
                fqe_ops.append(fop)
            elif isinstance(fop, SumOfSquaresOperator):
                fqe_ops.append(fop)
            else:
                fqe_ops.append(
                    build_hamiltonian(1j * fop, self.sdim,
                                      conserve_number=True))

            operator_pool_fqe.extend(fqe_ops)
            existing_parameters.extend([0])

            if self.num_opt_var is not None:
                if len(operator_pool_fqe) < self.num_opt_var:
                    pool_to_op = operator_pool_fqe
                    params_to_op = existing_parameters
                    current_wf = copy.deepcopy(initial_wf)
                else:
                    pool_to_op = operator_pool_fqe[-self.num_opt_var:]
                    params_to_op = existing_parameters[-self.num_opt_var:]
                    current_wf = copy.deepcopy(initial_wf)
                    for fqe_op, coeff in zip(
                            operator_pool_fqe[:-self.num_opt_var],
                            existing_parameters[:-self.num_opt_var]):
                        current_wf = current_wf.time_evolve(coeff, fqe_op)
                    temp_cwf = copy.deepcopy(current_wf)
                    for fqe_op, coeff in zip(pool_to_op, params_to_op):
                        if np.isclose(coeff, 0):
                            continue
                        temp_cwf = temp_cwf.time_evolve(coeff, fqe_op)

                new_parameters, current_e = self.optimize_param(
                    pool_to_op,
                    params_to_op,
                    current_wf,
                    opt_method,
                    opt_options=opt_options)

                if len(operator_pool_fqe) < self.num_opt_var:
                    existing_parameters = new_parameters.tolist()
                else:
                    existing_parameters[-self.num_opt_var:] = \
                        new_parameters.tolist()
            else:
                new_parameters, current_e = self.optimize_param(
                    operator_pool_fqe,
                    existing_parameters,
                    initial_wf,
                    opt_method,
                    opt_options=opt_options)
                existing_parameters = new_parameters.tolist()

            if self.verbose:
                print(iteration, current_e, np.max(np.abs(acse_residual)),
                      len(existing_parameters))
            self.energies.append(current_e)
            self.residuals.append(acse_residual)
            if np.max(np.abs(acse_residual)) < self.stopping_eps or np.abs(
                    self.energies[-2] - self.energies[-1]) < self.delta_e_eps:
                break
            iteration += 1
Ejemplo n.º 6
0
    def bc_solve_rdms(self, initial_wf):
        """Propagate BC differential equation until convergence.

        State is evolved and then 3-RDM is measured.  This information is
        used to construct a new state

        Args:
            initial_wf: Initial wavefunction to evolve.
        """
        fqe_wf = copy.deepcopy(initial_wf)
        iter_max = self.iter_max
        iteration = 0
        sector = (self.nalpha + self.nbeta, self.sz)
        h = 1.0e-4
        self.acse_energy = [fqe_wf.expectationValue(self.elec_hamil).real]
        while iteration < iter_max:
            # extract FqeData object each iteration in case fqe_wf is copied
            fqe_data = fqe_wf.sector(sector)
            # get RDMs from FqeData
            d3 = fqe_data.get_three_pdm()
            _, tpdm = fqe_data.get_openfermion_rdms()

            # get ACSE Residual and 2-RDM gradient
            acse_residual = two_rdo_commutator_symm(
                self.reduced_ham.two_body_tensor, tpdm, d3)
            tpdm_grad = two_rdo_commutator_antisymm(acse_residual, tpdm, d3)
            acse_res_op = get_fermion_op(acse_residual)

            # epsilon_opt = - Tr[K, D'(lambda)] / Tr[K, D''(lambda)]
            # K is reduced Hamiltonian
            # get approximate D'' by short propagation
            # TODO: do this with cumulant reconstruction instead of wf prop.
            fqe_wfh = fqe_wf.time_evolve(h, 1j * acse_res_op)
            fqe_datah = fqe_wfh.sector(sector)
            d3h = fqe_datah.get_three_pdm()
            _, tpdmh = fqe_datah.get_openfermion_rdms()
            acse_residualh = two_rdo_commutator_symm(
                self.reduced_ham.two_body_tensor, tpdmh, d3h)
            tpdm_gradh = two_rdo_commutator_antisymm(acse_residualh, tpdmh,
                                                     d3h)

            tpdm_gradgrad = (1 / h) * (tpdm_gradh - tpdm_grad)
            epsilon = -np.einsum("ijkl,ijkl", self.reduced_ham.two_body_tensor,
                                 tpdm_grad)
            epsilon /= np.einsum("ijkl,ijkl", self.reduced_ham.two_body_tensor,
                                 tpdm_gradgrad)
            epsilon = epsilon.real

            fqe_wf = fqe_wf.time_evolve(epsilon, 1j * acse_res_op)
            current_energy = fqe_wf.expectationValue(self.elec_hamil).real
            self.acse_energy.append(current_energy.real)

            print_string = "Iter {: 5f}\tcurrent energy {: 5.10f}\t".format(
                iteration, current_energy)
            print_string += "|dE| {: 5.10f}\tStep size {: 5.10f}".format(
                np.abs(self.acse_energy[-2] - self.acse_energy[-1]), epsilon)

            if self.verbose:
                print(print_string)

            if (iteration >= 1
                    and np.abs(self.acse_energy[-2] - self.acse_energy[-1]) <
                    0.5e-4):
                break
            iteration += 1