def evolve_fqe_givens_unrestricted(wfn: Wavefunction, u: np.ndarray) -> Wavefunction: """Evolve a wavefunction by u generated from a 1-body Hamiltonian. Args: wfn: 2^{n} x 1 vector. u: (n x n) unitary matrix. Returns: New evolved wfn object. """ rotations, diagonal = givens_decomposition_square(u.copy()) # Iterate through each layer and time evolve by the appropriate # fermion operators for layer in rotations: for givens in layer: i, j, theta, phi = givens if not np.isclose(phi, 0): op = of.FermionOperator(((j, 1), (j, 0)), coefficient=-phi) wfn = wfn.time_evolve(1.0, op) if not np.isclose(theta, 0): op = of.FermionOperator( ((i, 1), (j, 0)), coefficient=-1j * theta) + of.FermionOperator( ((j, 1), (i, 0)), coefficient=1j * theta) wfn = wfn.time_evolve(1.0, op) # evolve the last diagonal phases for idx, final_phase in enumerate(diagonal): if not np.isclose(final_phase, 1.0): op = of.FermionOperator(((idx, 1), (idx, 0)), -np.angle(final_phase)) wfn = wfn.time_evolve(1.0, op) return wfn
def evolve_fqe_givens_sector(wfn: Wavefunction, u: np.ndarray, sector='alpha') -> Wavefunction: """Evolve a wavefunction by u generated from a 1-body Hamiltonian. Args: wfn: FQE Wavefunction on n-orbitals u: (n x n) unitary matrix. sector: Optional either 'alpha' or 'beta' indicating which sector to rotate Returns: New evolved wfn object. """ if sector == 'alpha': sigma = 0 elif sector == 'beta': sigma = 1 else: raise ValueError("Bad section variable. Either (alpha) or (beta)") if not np.isclose(u.shape[0], wfn.norb()): raise ValueError( "unitary is not specified for the correct number of orbitals") rotations, diagonal = givens_decomposition_square(u.copy()) # Iterate through each layer and time evolve by the appropriate # fermion operators for layer in rotations: for givens in layer: i, j, theta, phi = givens if not np.isclose(phi, 0): op = of.FermionOperator( ((2 * j + sigma, 1), (2 * j + sigma, 0)), coefficient=-phi) wfn = wfn.time_evolve(1.0, op) if not np.isclose(theta, 0): op = of.FermionOperator(((2 * i + sigma, 1), (2 * j + sigma, 0)), coefficient=-1j * theta) + \ of.FermionOperator(((2 * j + sigma, 1), (2 * i + sigma, 0)), coefficient=1j * theta) wfn = wfn.time_evolve(1.0, op) # evolve the last diagonal phases for idx, final_phase in enumerate(diagonal): if not np.isclose(final_phase, 1.0): op = of.FermionOperator( ((2 * idx + sigma, 1), (2 * idx + sigma, 0)), -np.angle(final_phase)) wfn = wfn.time_evolve(1.0, op) return wfn
def evolve_fqe_charge_charge_sector(wfn: Wavefunction, vij_mat: np.ndarray, sector='alpha', time=1) -> Wavefunction: r"""Utility for testing evolution of a full 2^{n} wavefunction via :math:`exp{-i time * \sum_{i,j}v_{i, j}n_{i, alpha}n_{j, beta}}.` Args: wfn: fqe_wf with sdim = n vij_mat: List[(n x n] matrices n is the spatial orbital rank time: evolution time. Returns: New evolved 2^{2 * n} x 1 vector """ if sector == 'alpha': sigma = 0 elif sector == 'beta': sigma = 1 else: raise ValueError("Sector must be alpha or beta.") norbs = vij_mat.shape[0] for p, q in product(range(norbs), repeat=2): if np.isclose(vij_mat[p, q], 0): continue fop = of.FermionOperator(((2 * p + sigma, 1), (2 * p + sigma, 0), (2 * q + sigma, 1), (2 * q + sigma, 0)), coefficient=vij_mat[p, q]) wfn = wfn.time_evolve(time, fop) return wfn
def test_apply_individual_nbody_error(self): fop = FermionOperator('1^ 0') fop += FermionOperator('2^ 0') fop += FermionOperator('2^ 1') hamil = sparse_hamiltonian.SparseHamiltonian(fop) wfn = Wavefunction([[2, 0, 2]], broken=['spin']) self.assertRaises(ValueError, wfn._apply_individual_nbody, hamil) self.assertRaises(ValueError, wfn._evolve_individual_nbody, 0.1, hamil) fop = FermionOperator('1^ 0') fop += FermionOperator('2^ 0') hamil = sparse_hamiltonian.SparseHamiltonian(fop) self.assertRaises(ValueError, wfn._evolve_individual_nbody, 0.1, hamil) fop = FermionOperator('1^ 0', 1.0) fop += FermionOperator('0^ 1', 0.9) hamil = sparse_hamiltonian.SparseHamiltonian(fop) self.assertRaises(ValueError, wfn._evolve_individual_nbody, 0.1, hamil) fop = FermionOperator('1^ 0^') hamil = sparse_hamiltonian.SparseHamiltonian(fop) self.assertRaises(ValueError, wfn._apply_individual_nbody, hamil) self.assertRaises(ValueError, wfn._evolve_individual_nbody, 0.1, hamil) self.assertRaises(TypeError, wfn._evolve_individual_nbody, 0.1, 1)
def test_apply_type_error(self): data = numpy.zeros((2, 2), dtype=numpy.complex128) wfn = Wavefunction([[2, 0, 2]], broken=['spin']) hamil = general_hamiltonian.General((data, )) hamil._conserve_number = False self.assertRaises(TypeError, wfn.apply, hamil) self.assertRaises(TypeError, wfn.time_evolve, 0.1, hamil) wfn = Wavefunction([[2, 0, 2]], broken=['number']) hamil = general_hamiltonian.General((data, )) self.assertRaises(TypeError, wfn.apply, hamil) self.assertRaises(TypeError, wfn.time_evolve, 0.1, hamil) wfn = Wavefunction([[2, 0, 2]]) hamil = get_restricted_hamiltonian((data, )) self.assertRaises(ValueError, wfn.time_evolve, 0.1, hamil, True)
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_apply_spinful_fermionop(self): """ Make sure the spin-orbital reordering is working by comparing apply operation """ wfn = Wavefunction([[2, 0, 2]]) wfn.set_wfn(strategy='random') wfn.normalize() cirq_wf = to_cirq(wfn).reshape((-1, 1)) op_to_apply = FermionOperator() test_state = copy.deepcopy(wfn) test_state.set_wfn('zero') for p, q, r, s in product(range(2), repeat=4): op = FermionOperator( ((2 * p, 1), (2 * q + 1, 1), (2 * r + 1, 0), (2 * s, 0)), coefficient=numpy.random.randn()) op_to_apply += op + hermitian_conjugated(op) test_state += wfn.apply(op + hermitian_conjugated(op)) opmat = get_sparse_operator(op_to_apply, n_qubits=4).toarray() new_state_cirq = opmat @ cirq_wf # this part is because we need to pass a normalized wavefunction norm_constant = new_state_cirq.conj().T @ new_state_cirq new_state_cirq /= numpy.sqrt(norm_constant) new_state_wfn = from_cirq(new_state_cirq.flatten(), thresh=1.0E-12) new_state_wfn.scale(numpy.sqrt(norm_constant)) self.assertTrue( numpy.allclose(test_state.get_coeff((2, 0)), new_state_wfn.get_coeff((2, 0))))
def test_evolve_spinful_fermionop(self): """ Make sure the spin-orbital reordering is working by comparing time evolution """ wfn = Wavefunction([[2, 0, 2]]) wfn.set_wfn(strategy='random') wfn.normalize() cirq_wf = to_cirq(wfn).reshape((-1, 1)) op_to_apply = FermionOperator() for p, q, r, s in product(range(2), repeat=4): op = FermionOperator( ((2 * p, 1), (2 * q + 1, 1), (2 * r + 1, 0), (2 * s, 0)), coefficient=numpy.random.randn()) op_to_apply += op + hermitian_conjugated(op) opmat = get_sparse_operator(op_to_apply, n_qubits=4).toarray() dt = 0.765 new_state_cirq = scipy.linalg.expm(-1j * dt * opmat) @ cirq_wf new_state_wfn = from_cirq(new_state_cirq.flatten(), thresh=1.0E-12) test_state = wfn.time_evolve(dt, op_to_apply) self.assertTrue( numpy.allclose(test_state.get_coeff((2, 0)), new_state_wfn.get_coeff((2, 0))))
def test_general_exceptions(self): """Test general method exceptions """ test1 = Wavefunction(param=[[2, 0, 4]]) test2 = Wavefunction(param=[[4, -4, 8]]) test1.set_wfn(strategy='ones') test2.set_wfn(strategy='ones') self.assertRaises(ValueError, test1.ax_plus_y, 1.0, test2) self.assertRaises(ValueError, test1.__add__, test2) self.assertRaises(ValueError, test1.__sub__, test2) self.assertRaises(ValueError, test1.set_wfn, strategy='from_data')
def test_lih_dipole(self): """Calculate the LiH dipole """ norb = 6 nalpha = 2 nbeta = 2 nele = nalpha + nbeta au2debye = 2.5417464157449032 dip_ref, dip_mat, lih_ground = build_lih_data.build_lih_data('dipole') wfn = Wavefunction([[nele, nalpha - nbeta, norb]]) wfn.set_wfn(strategy='from_data', raw_data={(nele, nalpha - nbeta): lih_ground}) hwfn_x = wfn._apply_array(tuple([dip_mat[0]]), e_0=0. + 0.j) hwfn_y = wfn._apply_array(tuple([dip_mat[1]]), e_0=0. + 0.j) hwfn_z = wfn._apply_array(tuple([dip_mat[2]]), e_0=0. + 0.j) calc_dip = numpy.array([fqe.vdot(wfn, hwfn_x).real, \ fqe.vdot(wfn, hwfn_y).real, \ fqe.vdot(wfn, hwfn_z).real])*au2debye for card in range(3): with self.subTest(dip=card): err = abs(calc_dip[card] - dip_ref[card]) self.assertTrue(err < 1.e-5)
def test_save_read(self): """Check that the wavefunction can be properly archived and retieved """ numpy.random.seed(seed=409) wfn = get_number_conserving_wavefunction(3, 3) wfn.set_wfn(strategy='random') wfn.save('test_save_read') read_wfn = Wavefunction() read_wfn.read('test_save_read') for key in read_wfn.sectors(): self.assertTrue( numpy.allclose(read_wfn._civec[key].coeff, wfn._civec[key].coeff)) self.assertEqual(read_wfn._symmetry_map, wfn._symmetry_map) self.assertEqual(read_wfn._conserved, wfn._conserved) self.assertEqual(read_wfn._conserve_spin, wfn._conserve_spin) self.assertEqual(read_wfn._conserve_number, wfn._conserve_number) self.assertEqual(read_wfn._norb, wfn._norb) os.remove('test_save_read') wfn = get_spin_conserving_wavefunction(2, 6) wfn.set_wfn(strategy='random') wfn.save('test_save_read') read_wfn = Wavefunction() read_wfn.read('test_save_read') for key in read_wfn.sectors(): self.assertTrue( numpy.allclose(read_wfn._civec[key].coeff, wfn._civec[key].coeff)) self.assertEqual(read_wfn._symmetry_map, wfn._symmetry_map) self.assertEqual(read_wfn._conserved, wfn._conserved) self.assertEqual(read_wfn._conserve_spin, wfn._conserve_spin) self.assertEqual(read_wfn._conserve_number, wfn._conserve_number) self.assertEqual(read_wfn._norb, wfn._norb) os.remove('test_save_read')
def test_apply_number(self): norb = 4 test = numpy.random.rand(norb, norb) diag = numpy.random.rand(norb * 2) diag2 = copy.deepcopy(diag) e_0 = 0 for i in range(norb): e_0 += diag[i + norb] diag2[i + norb] = -diag[i + norb] hamil = diagonal_hamiltonian.Diagonal(diag2, e_0=e_0) hamil._conserve_number = False wfn = Wavefunction([[4, 2, norb]], broken=['number']) wfn.set_wfn(strategy='from_data', raw_data={(4, 2): test}) out1 = wfn.apply(hamil) hamil = diagonal_hamiltonian.Diagonal(diag) wfn = Wavefunction([[4, 2, norb]]) wfn.set_wfn(strategy='from_data', raw_data={(4, 2): test}) out2 = wfn.apply(hamil) self.assertTrue( numpy.allclose(out1._civec[(4, 2)].coeff, out2._civec[(4, 2)].coeff))
def evolve_fqe_diagaonal_coulomb(wfn: Wavefunction, vij_mat: np.ndarray, time=1) -> Wavefunction: r"""Utility for testing evolution of a full 2^{n} wavefunction via :math:`exp{-i time * \sum_{i,j, sigma, tau}v_{i, j}n_{i\sigma}n_{j\tau}}.` Args: wfn: 2^{n} x 1 vector. vij_mat: List[(n//2 x n//2)] matrices time: evolution time. Returns: New evolved 2^{n} x 1 vector """ dc_ham = DiagonalCoulomb(vij_mat) return wfn.time_evolve(time, dc_ham)
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_apply_diagonal(self): wfn = Wavefunction([[2, 0, 2]]) wfn.set_wfn(strategy='random') data = numpy.random.rand(2) hamil = diagonal_hamiltonian.Diagonal(data) out1 = wfn._apply_diagonal(hamil) fac = 0.5 hamil = diagonal_hamiltonian.Diagonal(data, e_0=fac) out2 = wfn._apply_diagonal(hamil) out2.ax_plus_y(-fac, wfn) self.assertTrue((out1 - out2).norm() < 1.0e-8)
def test_rdm(self): """Check that the rdms will properly return the energy """ wfn = Wavefunction(param=[[4, 0, 3]]) work, energy = build_wfn.restricted_wfn_energy() wfn.set_wfn(strategy='from_data', raw_data={(4, 0): work}) rdm1 = wfn.rdm('i^ j') rdm2 = wfn.rdm('i^ j^ k l') rdm3 = wfn.rdm('i^ j^ k^ l m n') rdm4 = wfn.rdm('i^ j^ k^ l^ m n o p') h1e, h2e, h3e, h4e = build_hamiltonian.build_restricted(3, full=False) expval = 0. + 0.j axes = [0, 1] expval += numpy.tensordot(h1e, rdm1, axes=(axes, axes)) axes = [0, 1, 2, 3] expval += numpy.tensordot(h2e, rdm2, axes=(axes, axes)) axes = [0, 1, 2, 3, 4, 5] expval += numpy.tensordot(h3e, rdm3, axes=(axes, axes)) axes = [0, 1, 2, 3, 4, 5, 6, 7] expval += numpy.tensordot(h4e, rdm4, axes=(axes, axes)) self.assertAlmostEqual(expval, energy)
def test_apply_nbody(self): wfn = Wavefunction([[2, 0, 2]]) wfn.set_wfn(strategy='random') fac = 3.14 fop = FermionOperator('1^ 1', fac) hamil = sparse_hamiltonian.SparseHamiltonian(fop) out1 = wfn._apply_few_nbody(hamil) fop = FermionOperator('1 1^', fac) hamil = sparse_hamiltonian.SparseHamiltonian(fop) out2 = wfn._apply_few_nbody(hamil) out2.scale(-1.0) out2.ax_plus_y(fac, wfn) self.assertTrue((out1 - out2).norm() < 1.0e-8)
def test_wick(self): """Check that wick performs the proper restructuring of the density matrix given a string of indexes. """ norb = 4 nele = 4 s_z = 0 wfn = Wavefunction([[nele, s_z, norb]]) numpy.random.seed(seed=1) wfn.set_wfn(strategy='random') wfn.normalize() rdms = wfn._compute_rdm(4) out1 = wick.wick('k j^', list(rdms), True) two = numpy.eye(norb, dtype=out1.dtype) * 2.0 self.assertRaises(ValueError, wick.wick, 'k0 j', list(rdms)) self.assertTrue(numpy.allclose(two - out1.T, rdms[0])) self.assertRaises(ValueError, wick.wick, 'k^ l i^ j', list(rdms), True) out2 = wick.wick('k l i^ j^', list(rdms), True) h_1 = numpy.zeros_like(out1) for i in range(norb): h_1[:, :] += out2[:, i, :, i] / (norb * 2 - nele - 1) self.assertAlmostEqual(numpy.std(out1 + h_1), 0.) out2a = wick.wick('k l^ i^ j', list(rdms), True) self.assertAlmostEqual(out2a[2, 3, 0, 1], -rdms[1][0, 3, 2, 1]) out3 = wick.wick('k l m i^ j^ n^', list(rdms), True) h_2 = numpy.zeros_like(out2) for i in range(norb): h_2[:, :, :, :] += out3[:, i, :, :, i, :] / (norb * 2 - nele - 2) self.assertAlmostEqual(numpy.std(out2 - h_2), 0.) out4 = wick.wick('k l m x i^ j^ n^ y^', list(rdms), True) h_3 = numpy.zeros_like(out3) for i in range(norb): h_3[:, :, :, :, :, :] += out4[:, i, :, :, :, i, :, :] / (norb * 2 - nele - 3) self.assertAlmostEqual(numpy.std(out3 + h_3), 0.)
def evolve_fqe_charge_charge_unrestricted(wfn: Wavefunction, vij_mat: np.ndarray, time=1) -> Wavefunction: r"""Utility for testing evolution of a full 2^{n} wavefunction via :math:`exp{-i time * \sum_{i,j}v_{i, j}n_{i}n_{j}}.` Args: wfn: fqe_wf with sdim = n vij_mat: List[(n x n] matrices time: evolution time. Returns: New evolved 2^{n} x 1 vector """ nso = vij_mat.shape[0] for p, q in product(range(nso), repeat=2): if np.isclose(vij_mat[p, q], 0): continue fop = of.FermionOperator(((p, 1), (p, 0), (q, 1), (q, 0)), coefficient=vij_mat[p, q]) wfn = wfn.time_evolve(time, fop) return wfn
def test_general_functions(self): """Test general wavefunction members """ test = Wavefunction(param=[[2, 0, 4]]) test.set_wfn(strategy='ones') self.assertEqual(1. + 0.j, test[(4, 8)]) test[(4, 8)] = 3.14 + 0.00159j self.assertEqual(3.14 + 0.00159j, test[(4, 8)]) self.assertEqual(3.14 + 0.00159j, test.max_element()) self.assertTrue(test.conserve_spin()) test1 = Wavefunction(param=[[2, 0, 4]]) test2 = Wavefunction(param=[[2, 0, 4]]) test1.set_wfn(strategy='ones') test2.set_wfn(strategy='ones') work = test1 + test2 ref = 2.0 * numpy.ones((4, 4), dtype=numpy.complex128) self.assertTrue(numpy.allclose(ref, work._civec[(2, 0)].coeff)) work = test1 - test2 ref = numpy.zeros((4, 4), dtype=numpy.complex128) self.assertTrue(numpy.allclose(ref, work._civec[(2, 0)].coeff))
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
def test_expectation_value_type_error(self): wfn = Wavefunction([[4, 0, 4]]) self.assertRaises(TypeError, wfn.expectationValue, 1)
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 get_acse_residual_fqe(fqe_wf: Wavefunction, fqe_ham: RestrictedHamiltonian, norbs: int) -> np.ndarray: """Get the ACSE block by using reduced density operators that are Sz spin adapted R^{ij}_{lk} = <psi | [i^ j^ k l, A] | psi> alpha-alpha, beta-beta, alpha-beta, and beta-alpha blocks we do not compression over alpha-alpha or beta-beta so these are still norbs**2 in linear dimension. In other words, we do computation on elements we know should be zero. This is for simplicity in the code. Args: fqe_wf: fqe.Wavefunction object to calculate expectation value with fqe_ham: fqe.RestrictedHamiltonian operator corresponding to a chemical Hamiltonian norbs: Number of orbitals. Number of spatial orbitals Returns: Gradient of the i^ j^ k l operator """ acse_aa = np.zeros((norbs, norbs, norbs, norbs), dtype=np.complex128) acse_bb = np.zeros((norbs, norbs, norbs, norbs), dtype=np.complex128) acse_ab = np.zeros((norbs, norbs, norbs, norbs), dtype=np.complex128) fqe_appA = fqe_wf.apply(fqe_ham) for p, q, r, s in product(range(norbs), repeat=4): # alpha-alpha block real if p != q and r != s: rdo = ((2 * p, 1), (2 * q, 1), (2 * r, 0), (2 * s, 0)) rdo = 1j * (of.FermionOperator(rdo) - of.hermitian_conjugated(of.FermionOperator(rdo))) val1 = fqe.util.vdot(fqe_appA, fqe_wf.apply(rdo)) val2 = np.conjugate(val1) acse_aa[p, q, r, s] = (val2 - val1) / 2j # alpha-alpha block imag rdo = ((2 * p, 1), (2 * q, 1), (2 * r, 0), (2 * s, 0)) rdo = of.FermionOperator(rdo) + of.hermitian_conjugated( of.FermionOperator(rdo)) val1 = fqe.util.vdot(fqe_appA, fqe_wf.apply(rdo)) val2 = np.conjugate(val1) acse_aa[p, q, r, s] += (val2 - val1) / 2 # beta-beta block real rdo = ( (2 * p + 1, 1), (2 * q + 1, 1), (2 * r + 1, 0), (2 * s + 1, 0), ) rdo = 1j * (of.FermionOperator(rdo) - of.hermitian_conjugated(of.FermionOperator(rdo))) val1 = fqe.util.vdot(fqe_appA, fqe_wf.apply(rdo)) val2 = np.conjugate(val1) acse_bb[p, q, r, s] += (val2 - val1) / 2j # beta-beta block imag rdo = ( (2 * p + 1, 1), (2 * q + 1, 1), (2 * r + 1, 0), (2 * s + 1, 0), ) rdo = of.FermionOperator(rdo) + of.hermitian_conjugated( of.FermionOperator(rdo)) val1 = fqe.util.vdot(fqe_appA, fqe_wf.apply(rdo)) val2 = np.conjugate(val1) acse_bb[p, q, r, s] += (val2 - val1) / 2 # alpha-beta block real rdo = ((2 * p, 1), (2 * q + 1, 1), (2 * r + 1, 0), (2 * s, 0)) rdo = 1j * (of.FermionOperator(rdo) - of.hermitian_conjugated(of.FermionOperator(rdo))) val1 = fqe.util.vdot(fqe_appA, fqe_wf.apply(rdo)) val2 = np.conjugate(val1) acse_ab[p, q, r, s] += (val2 - val1) / 2j # alpha-beta block imag rdo = ((2 * p, 1), (2 * q + 1, 1), (2 * r + 1, 0), (2 * s, 0)) rdo = of.FermionOperator(rdo) + of.hermitian_conjugated( of.FermionOperator(rdo)) val1 = fqe.util.vdot(fqe_appA, fqe_wf.apply(rdo)) val2 = np.conjugate(val1) # fqe.util.vdot(fqe_wf.apply(rdo), fqe_appA) acse_ab[p, q, r, s] += (val2 - val1) / 2 # unroll residual blocks into full matrix acse_residual = np.zeros((2 * norbs, 2 * norbs, 2 * norbs, 2 * norbs), dtype=np.complex128) acse_residual[::2, ::2, ::2, ::2] = acse_aa acse_residual[1::2, 1::2, 1::2, 1::2] = acse_bb acse_residual[::2, 1::2, 1::2, ::2] = acse_ab acse_residual[::2, 1::2, ::2, 1::2] = np.einsum("ijkl->ijlk", -acse_ab) acse_residual[1::2, ::2, ::2, 1::2] = np.einsum("ijkl->jilk", acse_ab) acse_residual[1::2, ::2, 1::2, ::2] = np.einsum("ijkl->ijlk", -acse_residual[1::2, ::2, ::2, 1::2]) return acse_residual
def test_set_wfn_random_with_multiple_sectors_is_normalized(self): wfn = Wavefunction([[2, 0, 4], [2, -2, 4]], broken=None) wfn.set_wfn(strategy="random") self.assertAlmostEqual(wfn.norm(), 1.0)