def test_lih_ops(self): """Check the value of the operators on LiH """ norb = 6 nalpha = 2 nbeta = 2 nele = nalpha + nbeta _, _, lih_ground = build_lih_data.build_lih_data('energy') wfn = Wavefunction([[nele, nalpha - nbeta, norb]]) wfn.set_wfn(strategy='from_data', raw_data={(nele, nalpha - nbeta): lih_ground}) operator = S2Operator() self.assertAlmostEqual(wfn.expectationValue(operator), 0. + 0.j) operator = SzOperator() self.assertAlmostEqual(wfn.expectationValue(operator), 0. + 0.j) operator = TimeReversalOp() self.assertAlmostEqual(wfn.expectationValue(operator), 1. + 0.j) operator = NumberOperator() self.assertAlmostEqual(wfn.expectationValue(operator), 4. + 0.j) self.assertAlmostEqual(wfn.expectationValue(operator, wfn), 4. + 0.j)
def test_lih_energy(self): """Checking total energy with LiH """ eref = -8.877719570384043 norb = 6 nalpha = 2 nbeta = 2 nele = nalpha + nbeta h1e, h2e, lih_ground = build_lih_data.build_lih_data('energy') elec_hamil = general_hamiltonian.General((h1e, h2e)) wfn = Wavefunction([[nele, nalpha - nbeta, norb]]) wfn.set_wfn(strategy='from_data', raw_data={(nele, nalpha - nbeta): lih_ground}) ecalc = wfn.expectationValue(elec_hamil) self.assertAlmostEqual(eref, ecalc, places=8)
def test_hartree_fock_init(self): h1e, h2e, _ = build_lih_data('energy') elec_hamil = get_restricted_hamiltonian((h1e, h2e)) norb = 6 nalpha = 2 nbeta = 2 wfn = Wavefunction([[nalpha + nbeta, nalpha - nbeta, norb]]) wfn.print_wfn() wfn.set_wfn(strategy='hartree-fock') wfn.print_wfn() self.assertEqual(wfn.expectationValue(elec_hamil), -8.857341498221992) hf_wf = numpy.zeros((int(binom(norb, 2)), int(binom(norb, 2)))) hf_wf[0, 0] = 1. self.assertTrue(numpy.allclose(wfn.get_coeff((4, 0)), hf_wf)) wfn = Wavefunction([[nalpha + nbeta, nalpha - nbeta, norb], [nalpha + nbeta, 2, norb]]) self.assertRaises(ValueError, wfn.set_wfn, strategy='hartree-fock')
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
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