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 fermion_generator(self) -> openfermion.FermionOperator: """The FermionOperator G such that the gate's unitary is exp(-i t G) with exponent t using the Jordan-Wigner transformation.""" half_generator = sum(( w * G for w, G in zip(self.weights, self.fermion_generator_components())), openfermion.FermionOperator()) return half_generator + openfermion.hermitian_conjugated(half_generator)
def operations(self, qubits: Sequence[cirq.Qid]) -> cirq.OP_TREE: """Returns qubit operators based on STO-3G and UCCSD ansatz""" _symbols = list(self.params()) _ucc_operator = self._fermionic_operators # self._get_fermionic_operators() for i, fo in enumerate(_ucc_operator[::2]): # Jordan-Wigner transform each fermionic operator + its Hermitian conjugate qo_list = list(of.jordan_wigner(fo - of.hermitian_conjugated(fo))) yield IGeneralizedUCCSD.qubit_to_gate_operators( qubit_operators=qo_list, qubits=qubits, par=_symbols[i])
def test_qubitop_to_dict_io(self): # Given qubit_op = QubitOperator(((0, 'Y'), (1, 'X'), (2, 'Z'), (4, 'X')), 3.j) qubit_op += hermitian_conjugated(qubit_op) # When qubitop_dict = convert_qubitop_to_dict(qubit_op) recreated_qubit_op = convert_dict_to_qubitop(qubitop_dict) # Then self.assertEqual(recreated_qubit_op, qubit_op)
def test_double_excitation_matches_fermionic_evolution(exponent): gate = ofc.DoubleExcitation ** exponent op = openfermion.FermionOperator('3^ 2^ 1 0') op += openfermion.hermitian_conjugated(op) matrix_op = openfermion.get_sparse_operator(op) time_evol_op = scipy.linalg.expm(-1j * matrix_op * exponent * numpy.pi) time_evol_op = time_evol_op.todense() cirq.testing.assert_allclose_up_to_global_phase( cirq.unitary(gate), time_evol_op, atol=1e-7)
def test_double_excitation_matches_fermionic_evolution(half_turns): gate = DoubleExcitation ** half_turns op = openfermion.FermionOperator('3^ 2^ 1 0') op += openfermion.hermitian_conjugated(op) matrix_op = openfermion.get_sparse_operator(op) time_evol_op = scipy.linalg.expm(-1j * matrix_op * half_turns * numpy.pi) time_evol_op = time_evol_op.todense() cirq.testing.assert_allclose_up_to_global_phase( gate.matrix(), time_evol_op, atol=1e-7)
def test_interaction_op_to_dict_io(self): # Given test_op = FermionOperator("1^ 2^ 3 4") test_op += hermitian_conjugated(test_op) interaction_op = get_interaction_operator(test_op) interaction_op.constant = 0.0 # When interaction_op_dict = convert_interaction_op_to_dict(interaction_op) recreated_interaction_op = convert_dict_to_interaction_op( interaction_op_dict) # Then self.assertEqual(recreated_interaction_op, interaction_op)
def test_interaction_operator_io(self): # Given test_op = FermionOperator("1^ 2^ 3 4") test_op += hermitian_conjugated(test_op) interaction_op = get_interaction_operator(test_op) interaction_op.constant = 0.0 # When save_interaction_operator(interaction_op, "interaction_op.json") loaded_op = load_interaction_operator("interaction_op.json") # Then self.assertEqual(interaction_op, loaded_op) os.remove("interaction_op.json")
def test_generalized_doubles(): generator = generate_antisymm_generator(2) nso = generator.shape[0] for p, q, r, s in product(range(nso), repeat=4): if p < q and s < r: assert np.isclose(generator[p, q, r, s], -generator[q, p, r, s]) ul, vl, one_body_residual, ul_ops, vl_ops, one_body_op = \ doubles_factorization_svd(generator) generator_mat = np.reshape(np.transpose(generator, [0, 3, 1, 2]), (nso**2, nso**2)).astype(np.float) one_body_residual_test = -np.einsum('pqrq->pr', generator) assert np.allclose(generator_mat, generator_mat.T) assert np.allclose(one_body_residual, one_body_residual_test) tgenerator_mat = np.zeros_like(generator_mat) for row_gem, col_gem in product(range(nso**2), repeat=2): p, s = row_gem // nso, row_gem % nso q, r = col_gem // nso, col_gem % nso tgenerator_mat[row_gem, col_gem] = generator[p, q, r, s] assert np.allclose(tgenerator_mat, generator_mat) u, sigma, vh = np.linalg.svd(generator_mat) fop = copy.deepcopy(one_body_op) fop2 = copy.deepcopy(one_body_op) fop3 = copy.deepcopy(one_body_op) fop4 = copy.deepcopy(one_body_op) for ll in range(len(sigma)): ul.append(np.sqrt(sigma[ll]) * u[:, ll].reshape((nso, nso))) ul_ops.append( get_fermion_op(np.sqrt(sigma[ll]) * u[:, ll].reshape((nso, nso)))) vl.append(np.sqrt(sigma[ll]) * vh[ll, :].reshape((nso, nso))) vl_ops.append( get_fermion_op(np.sqrt(sigma[ll]) * vh[ll, :].reshape((nso, nso)))) Smat = ul[ll] + vl[ll] Dmat = ul[ll] - vl[ll] S = ul_ops[ll] + vl_ops[ll] Sd = of.hermitian_conjugated(S) D = ul_ops[ll] - vl_ops[ll] Dd = of.hermitian_conjugated(D) op1 = S + 1j * of.hermitian_conjugated(S) op2 = S - 1j * of.hermitian_conjugated(S) op3 = D + 1j * of.hermitian_conjugated(D) op4 = D - 1j * of.hermitian_conjugated(D) assert np.isclose( of.normal_ordered(of.commutator( op1, of.hermitian_conjugated(op1))).induced_norm(), 0) assert np.isclose( of.normal_ordered(of.commutator( op2, of.hermitian_conjugated(op2))).induced_norm(), 0) assert np.isclose( of.normal_ordered(of.commutator( op3, of.hermitian_conjugated(op3))).induced_norm(), 0) assert np.isclose( of.normal_ordered(of.commutator( op4, of.hermitian_conjugated(op4))).induced_norm(), 0) fop3 += (1 / 8) * ((S**2 - Sd**2) - (D**2 - Dd**2)) fop4 += (1 / 16) * ((op1**2 + op2**2) - (op3**2 + op4**2)) op1mat = Smat + 1j * Smat.T op2mat = Smat - 1j * Smat.T op3mat = Dmat + 1j * Dmat.T op4mat = Dmat - 1j * Dmat.T assert np.allclose(of.commutator(op1mat, op1mat.conj().T), 0) assert np.allclose(of.commutator(op2mat, op2mat.conj().T), 0) assert np.allclose(of.commutator(op3mat, op3mat.conj().T), 0) assert np.allclose(of.commutator(op4mat, op4mat.conj().T), 0) # check that we have normal operators and that the outer product # of their eigenvalues is imaginary. Also check vv is unitary if not np.isclose(sigma[ll], 0): assert np.isclose( of.normal_ordered(get_fermion_op(op1mat) - op1).induced_norm(), 0) assert np.isclose( of.normal_ordered(get_fermion_op(op2mat) - op2).induced_norm(), 0) assert np.isclose( of.normal_ordered(get_fermion_op(op3mat) - op3).induced_norm(), 0) assert np.isclose( of.normal_ordered(get_fermion_op(op4mat) - op4).induced_norm(), 0) ww, vv = np.linalg.eig(op1mat) eye = np.eye(nso) assert np.allclose(np.outer(ww, ww).real, 0) assert np.allclose(vv.conj().T @ vv, eye) ww, vv = np.linalg.eig(op2mat) assert np.allclose(np.outer(ww, ww).real, 0) assert np.allclose(vv.conj().T @ vv, eye) ww, vv = np.linalg.eig(op3mat) assert np.allclose(np.outer(ww, ww).real, 0) assert np.allclose(vv.conj().T @ vv, eye) ww, vv = np.linalg.eig(op4mat) assert np.allclose(np.outer(ww, ww).real, 0) assert np.allclose(vv.conj().T @ vv, eye) fop2 += 0.25 * ul_ops[ll] * vl_ops[ll] fop2 += 0.25 * vl_ops[ll] * ul_ops[ll] fop2 += -0.25 * of.hermitian_conjugated( vl_ops[ll]) * of.hermitian_conjugated(ul_ops[ll]) fop2 += -0.25 * of.hermitian_conjugated( ul_ops[ll]) * of.hermitian_conjugated(vl_ops[ll]) fop += vl_ops[ll] * ul_ops[ll] true_fop = get_fermion_op(generator) assert np.isclose(of.normal_ordered(fop - true_fop).induced_norm(), 0) assert np.isclose(of.normal_ordered(fop2 - true_fop).induced_norm(), 0) assert np.isclose(of.normal_ordered(fop3 - true_fop).induced_norm(), 0) assert np.isclose(of.normal_ordered(fop4 - true_fop).induced_norm(), 0)
def test_random_evolution(): sdim = 2 nele = 2 generator = generate_antisymm_generator(2 * sdim) nso = generator.shape[0] for p, q, r, s in product(range(nso), repeat=4): if p < q and s < r: assert np.isclose(generator[p, q, r, s], -generator[q, p, r, s]) generator_mat = np.reshape(np.transpose(generator, [0, 3, 1, 2]), (nso**2, nso**2)).astype(np.float) _, sigma, _ = np.linalg.svd(generator_mat) ul, vl, _, ul_ops, vl_ops, _ = \ doubles_factorization_svd(generator) rwf = fqe.get_number_conserving_wavefunction(nele, sdim) # rwf = fqe.Wavefunction([[nele, 0, sdim]]) rwf.set_wfn(strategy='random') rwf.normalize() sigma_idx = np.where(sigma > 1.0E-13)[0] for ll in sigma_idx: Smat = ul[ll] + vl[ll] Dmat = ul[ll] - vl[ll] S = ul_ops[ll] + vl_ops[ll] D = ul_ops[ll] - vl_ops[ll] op1 = S + 1j * of.hermitian_conjugated(S) op2 = S - 1j * of.hermitian_conjugated(S) op3 = D + 1j * of.hermitian_conjugated(D) op4 = D - 1j * of.hermitian_conjugated(D) op1mat = Smat + 1j * Smat.T op2mat = Smat - 1j * Smat.T op3mat = Dmat + 1j * Dmat.T op4mat = Dmat - 1j * Dmat.T o1_rwf = rwf.time_evolve(1 / 16, 1j * op1**2) ww, vv = np.linalg.eig(op1mat) assert np.allclose(vv @ np.diag(ww) @ vv.conj().T, op1mat) trwf = evolve_fqe_givens_unrestricted(rwf, vv.conj().T) v_pq = np.outer(ww, ww) for p, q in product(range(nso), repeat=2): fop = of.FermionOperator(((p, 1), (p, 0), (q, 1), (q, 0)), coefficient=-v_pq[p, q].imag) trwf = trwf.time_evolve(1 / 16, fop) trwf = evolve_fqe_givens_unrestricted(trwf, vv) assert np.isclose(fqe.vdot(o1_rwf, trwf), 1) o_rwf = rwf.time_evolve(1 / 16, 1j * op2**2) ww, vv = np.linalg.eig(op2mat) assert np.allclose(vv @ np.diag(ww) @ vv.conj().T, op2mat) trwf = evolve_fqe_givens_unrestricted(rwf, vv.conj().T) v_pq = np.outer(ww, ww) for p, q in product(range(nso), repeat=2): fop = of.FermionOperator(((p, 1), (p, 0), (q, 1), (q, 0)), coefficient=-v_pq[p, q].imag) trwf = trwf.time_evolve(1 / 16, fop) trwf = evolve_fqe_givens_unrestricted(trwf, vv) assert np.isclose(fqe.vdot(o_rwf, trwf), 1) o_rwf = rwf.time_evolve(-1 / 16, 1j * op3**2) ww, vv = np.linalg.eig(op3mat) assert np.allclose(vv @ np.diag(ww) @ vv.conj().T, op3mat) trwf = evolve_fqe_givens_unrestricted(rwf, vv.conj().T) v_pq = np.outer(ww, ww) for p, q in product(range(nso), repeat=2): fop = of.FermionOperator(((p, 1), (p, 0), (q, 1), (q, 0)), coefficient=-v_pq[p, q].imag) trwf = trwf.time_evolve(-1 / 16, fop) trwf = evolve_fqe_givens_unrestricted(trwf, vv) assert np.isclose(fqe.vdot(o_rwf, trwf), 1) o_rwf = rwf.time_evolve(-1 / 16, 1j * op4**2) ww, vv = np.linalg.eig(op4mat) assert np.allclose(vv @ np.diag(ww) @ vv.conj().T, op4mat) trwf = evolve_fqe_givens_unrestricted(rwf, vv.conj().T) v_pq = np.outer(ww, ww) for p, q in product(range(nso), repeat=2): fop = of.FermionOperator(((p, 1), (p, 0), (q, 1), (q, 0)), coefficient=-v_pq[p, q].imag) trwf = trwf.time_evolve(-1 / 16, fop) trwf = evolve_fqe_givens_unrestricted(trwf, vv) assert np.isclose(fqe.vdot(o_rwf, trwf), 1)
def test_build_hamiltonian_paths(self): """Check that all cases of hamiltonian objects are built """ self.assertRaises(TypeError, build_hamiltonian, 0) with self.subTest(name='general'): ops = FermionOperator('1^ 4^ 0 3', 1.0) \ + FermionOperator('0^ 5^ 3 2^ 4^ 1 7 6', 1.2) \ + FermionOperator('1^ 6', -0.3) ops += hermitian_conjugated(ops) self.assertIsInstance(build_hamiltonian(ops), general_hamiltonian.General) with self.subTest(name='sparse'): ops = FermionOperator('5^ 1^ 3^ 2 0 1', 1.0 - 1.j) \ + FermionOperator('1^ 0^ 2^ 3 1 5', 1.0 + 1.j) self.assertIsInstance(build_hamiltonian(ops), sparse_hamiltonian.SparseHamiltonian) with self.subTest(name='diagonal'): ops = FermionOperator('1^ 1', 1.0) \ + FermionOperator('2^ 2', 2.0) \ + FermionOperator('3^ 3', 3.0) \ + FermionOperator('4^ 4', 4.0) self.assertIsInstance(build_hamiltonian(ops), diagonal_hamiltonian.Diagonal) with self.subTest(name='gso'): ops = FermionOperator() for i in range(4): for j in range(4): opstr = str(i) + '^ ' + str(j) coeff = complex((i + 1) * (j + 1) * 0.1) ops += FermionOperator(opstr, coeff) self.assertIsInstance(build_hamiltonian(ops), gso_hamiltonian.GSOHamiltonian) with self.subTest(name='restricted'): ops = FermionOperator() for i in range(0, 3, 2): for j in range(0, 3, 2): coeff = complex((i + 1) * (j + 1) * 0.1) opstr = str(i) + '^ ' + str(j) ops += FermionOperator(opstr, coeff) opstr = str(i + 1) + '^ ' + str(j + 1) ops += FermionOperator(opstr, coeff) self.assertIsInstance(build_hamiltonian(ops), restricted_hamiltonian.RestrictedHamiltonian) with self.subTest(name='sso'): ops = FermionOperator() for i in range(0, 3, 2): for j in range(0, 3, 2): coeff = complex((i + 1) * (j + 1) * 0.1) opstr = str(i) + '^ ' + str(j) ops += FermionOperator(opstr, coeff) opstr = str(i + 1) + '^ ' + str(j + 1) coeff *= 1.5 ops += FermionOperator(opstr, coeff) self.assertIsInstance(build_hamiltonian(ops), sso_hamiltonian.SSOHamiltonian) with self.subTest(name='diagonal_coulomb'): ops = FermionOperator() for i in range(4): for j in range(4): opstring = str(i) + '^ ' + str(j) + '^ ' + str( i) + ' ' + str(j) ops += FermionOperator(opstring, 0.001 * (i + 1) * (j + 1)) self.assertIsInstance(build_hamiltonian(ops), diagonal_coulomb.DiagonalCoulomb)
def multi_particle_ucc(h, reference=0, trotter_order=1, trotter_steps=1): """ UCC-style ansatz that doesn't preserve anything (i.e. uses all basis states). This is basically an implementation of create_arbitrary_state (however, theta will not match the coefficients in the superposition) built on pyquil.paulis.exponentiate_map. One idea is to use lowering and raising operators and "check operators" instead of Xs. This results in a more straight-forward mapping of theta to the coefficients since no previously produced state will be mapped to other states in a later stage. To clarify: with the current implementation the state |1 1 0> will both be produced by exp(X2 X1) operating on |0 0 0> and by exp(X2) operating on exp(X1)|0 0 0> (which contains a |0 1 0> term). This could improve potential bad properties with the current implementation. However, it might be difficult to create commuting hermitian terms, which is required in exponential_map_commuting_pauli_terms. If this function is called multiple times, particularly if theta has the same length in all calls, caching terms might significantly increase performance. @author: Joel :param np.ndarray h: The hamiltonian matrix. :param reference: integer representation of reference state :param trotter_order: Trotter order in trotterization :param trotter_steps: Trotter steps in trotterization :return: function(theta) which returns the ansatz Program. -1j*theta[i] is the coefficient in front of the term prod_k X_k^bit(i,k) where bit(i, k) is the k'th bit of i in binary, in the exponent. """ dim = h.shape[0] terms = [] for state in range(dim): if state != reference: term = QubitOperator(()) for qubit in range(int.bit_length(state)): if (state ^ reference) & (1 << qubit): # lower/raise qubit term *= QubitOperator((qubit, "X"), 1 / 2) \ + QubitOperator((qubit, "Y"), 1j * (int( reference & (1 << qubit) != 0) - 1 / 2)) else: # check that qubit has correct value (same as i and j) term *= QubitOperator((), 1 / 2) \ + QubitOperator((qubit, "Z"), 1 / 2 - int( reference & (1 << qubit) != 0)) terms.append( qubitop_to_pyquilpauli(term - hermitian_conjugated(term))) exp_maps = trotterize(terms, trotter_order, trotter_steps) def wrap(theta): """ Returns the ansatz Program. :param np.ndarray theta: parameters :return: the Program :rtype: pyquil.Program """ prog = Program() for qubit in range(int.bit_length(reference)): if reference & (1 << qubit): prog += X(qubit) for idx, exp_map in enumerate(exp_maps): for exp in exp_map: prog += exp(theta[idx]) return prog return wrap
def test_operators_initialization(coefficient: float): # 1. Let's create some basic operator operator = FermionOperator('2^ 1 3 7^', coefficient) alternative_operator = coefficient * FermionOperator(((2, 1), (1, 0), (3, 0), (6, 1))) sum_of_operators = operator + alternative_operator identity = FermionOperator('') zero = FermionOperator() # print(f'Fermion operator: {operator}; the terms are {list(operator.terms.keys())[0]}') print(f'Fermion operator: {operator}; the flattened terms are {operator.flattened_terms}') print(f'Alternative fermion operator: {alternative_operator}') print(f'Sum fermion operator: {sum_of_operators}; the terms are {sum_of_operators.terms}') # print(operator == alternative_operator) print(f'Identity operator: {identity}') print(f'Zero operator: {zero}') # 2. Checking operator combinations lhs_operator = FermionOperator('17^') rhs_operator = FermionOperator('19^') print(describe_result(lhs_operator, rhs_operator, lambda lhs, rhs: lhs * rhs, '*')) print(describe_result(lhs_operator, lhs_operator, lambda lhs, rhs: lhs * rhs, '*')) print(describe_result(lhs_operator, rhs_operator, lambda lhs, rhs: lhs + rhs, '+')) print(describe_result(lhs_operator, 5, lambda lhs, rhs: lhs ** rhs, '^')) # print(describe_result(lhs_operator * rhs_operator, lhs_operator, lambda lhs, rhs: lhs / rhs, '/')) # Throws an exception # 3. Use some auxiliary functions print(f'Operator {lhs_operator ** 5} occupies {count_qubits(lhs_operator)} qubits') print(f'Operator {rhs_operator ** 2} occupies {count_qubits(rhs_operator)} qubits') print(f'Operator {operator} occupies {count_qubits(operator)} qubits') print(f'Operator {operator} is {"not " if not operator.is_normal_ordered() else ""}normal ordered') print(f'Operator {(lhs_operator + rhs_operator)} is {"not " if not (lhs_operator + rhs_operator).is_normal_ordered() else ""}normal ordered') print(f'Operator {(lhs_operator * rhs_operator)} is {"not " if not (lhs_operator * rhs_operator).is_normal_ordered() else ""}normal ordered') print(f'Operator {(rhs_operator * lhs_operator)} is {"not " if not (rhs_operator * lhs_operator).is_normal_ordered() else ""}normal ordered') print(f'Commutator of {lhs_operator} and {rhs_operator} is {commutator(lhs_operator, rhs_operator)}') # 4. See qubit operators operator = QubitOperator('X1 Z3', coefficient) + QubitOperator('X20', coefficient - 1) print(f'Operator {operator} occupies {count_qubits(operator)} qubits') # 4. Perform transformations from fermion to qubit operators fermion_operator = FermionOperator_('5^', 0.1 + 0.2j) fermion_operator += hermitian_conjugated(fermion_operator) jw_operator = jordan_wigner(fermion_operator) bk_operator = bravyi_kitaev(fermion_operator) print() print('Source fermion operator:') print(fermion_operator) print() print('Source fermion operator with conjugate:') print(fermion_operator) print() print('Source fermion operator with conjugate passed through Jordan-Wigner transformation:') print(jw_operator) print() print('Eigenspectrum of the obtained operator:') print(eigenspectrum(jw_operator)) print() print('Reversed:') print(reverse_jordan_wigner(jw_operator)) print() print('Source fermion operator with conjugate passed through Bravyi-Kitaev transformation:') print(bk_operator) print() print('Eigenspectrum of the obtained operator:') print(eigenspectrum(bk_operator)) print()
def doubles_factorization_svd(generator_tensor: np.ndarray, eig_cutoff=None): """ Given an antisymmetric antihermitian tensor perform a double factorized low-rank decomposition. Given: A = sum_{pqrs}A^{pq}_{sr}p^ q^ r s with A^{pq}_{sr} = -A^{qp}_{sr} = -A^{pq}_{rs} = -A^{sr}_{pq} Rewrite A as a sum-of squares s.t A = sum_{l}Y_{l}^2 where Y_{l} are normal operator one-body operators such that the spectral theorem holds and we can use the double factorization to implement an approximate evolution. """ if not np.allclose(generator_tensor.imag, 0): raise TypeError("generator_tensor must be a real matrix") if eig_cutoff is not None: if eig_cutoff % 2 != 0: raise ValueError("eig_cutoff must be an even number") nso = generator_tensor.shape[0] generator_tensor = generator_tensor.real generator_mat = np.zeros((nso**2, nso**2)) for row_gem, col_gem in product(range(nso**2), repeat=2): p, s = row_gem // nso, row_gem % nso q, r = col_gem // nso, col_gem % nso generator_mat[row_gem, col_gem] = generator_tensor[p, q, r, s] test_generator_mat = np.reshape( np.transpose(generator_tensor, [0, 3, 1, 2]), (nso**2, nso**2)).astype(np.float) assert np.allclose(test_generator_mat, generator_mat) if not np.allclose(generator_mat, generator_mat.T): raise ValueError("generator tensor does not correspond to four-fold" " antisymmetry") one_body_residual = -np.einsum('pqrq->pr', generator_tensor) u, sigma, vh = np.linalg.svd(generator_mat) ul = [] ul_ops = [] vl = [] vl_ops = [] if eig_cutoff is None: max_sigma = len(sigma) else: max_sigma = eig_cutoff for ll in range(max_sigma): ul.append(np.sqrt(sigma[ll]) * u[:, ll].reshape((nso, nso))) ul_ops.append( get_fermion_op(np.sqrt(sigma[ll]) * u[:, ll].reshape((nso, nso)))) vl.append(np.sqrt(sigma[ll]) * vh[ll, :].reshape((nso, nso))) vl_ops.append( get_fermion_op(np.sqrt(sigma[ll]) * vh[ll, :].reshape((nso, nso)))) S = ul_ops[ll] + vl_ops[ll] D = ul_ops[ll] - vl_ops[ll] op1 = S + 1j * of.hermitian_conjugated(S) op2 = S - 1j * of.hermitian_conjugated(S) op3 = D + 1j * of.hermitian_conjugated(D) op4 = D - 1j * of.hermitian_conjugated(D) assert np.isclose( of.normal_ordered(of.commutator( op1, of.hermitian_conjugated(op1))).induced_norm(), 0) assert np.isclose( of.normal_ordered(of.commutator( op2, of.hermitian_conjugated(op2))).induced_norm(), 0) assert np.isclose( of.normal_ordered(of.commutator( op3, of.hermitian_conjugated(op3))).induced_norm(), 0) assert np.isclose( of.normal_ordered(of.commutator( op4, of.hermitian_conjugated(op4))).induced_norm(), 0) one_body_op = of.FermionOperator() for p, q in product(range(nso), repeat=2): tfop = ((p, 1), (q, 0)) one_body_op += of.FermionOperator(tfop, coefficient=one_body_residual[p, q]) return ul, vl, one_body_residual, ul_ops, vl_ops, one_body_op