def get_quadratic_hamiltonian(fermion_operator, chemical_potential=0., n_qubits=None): """Convert a quadratic fermionic operator to QuadraticHamiltonian. This function should only be called on fermionic operators which consist of only a_p^\dagger a_q, a_p^\dagger a_q^\dagger, and a_p a_q terms. Returns: quadratic_hamiltonian: An instance of the QuadraticHamiltonian class. Raises: TypeError: Input must be a FermionOperator. TypeError: FermionOperator does not map to QuadraticHamiltonian. Warning: Even assuming that each creation or annihilation operator appears at most a constant number of times in the original operator, the runtime of this method is exponential in the number of qubits. """ if not isinstance(fermion_operator, FermionOperator): raise TypeError('Input must be a FermionOperator.') if n_qubits is None: n_qubits = count_qubits(fermion_operator) if n_qubits < count_qubits(fermion_operator): raise ValueError('Invalid number of qubits specified.') # Normal order the terms and initialize. fermion_operator = normal_ordered(fermion_operator) constant = 0. combined_hermitian_part = numpy.zeros((n_qubits, n_qubits), complex) antisymmetric_part = numpy.zeros((n_qubits, n_qubits), complex) # Loop through terms and assign to matrix. for term in fermion_operator.terms: coefficient = fermion_operator.terms[term] # Ignore this term if the coefficient is zero if abs(coefficient) < EQ_TOLERANCE: continue if len(term) == 0: # Constant term constant = coefficient elif len(term) == 2: ladder_type = [operator[1] for operator in term] p, q = [operator[0] for operator in term] if ladder_type == [1, 0]: combined_hermitian_part[p, q] = coefficient elif ladder_type == [1, 1]: # Need to check that the corresponding [0, 0] term is present conjugate_term = ((p, 0), (q, 0)) if conjugate_term not in fermion_operator.terms: raise QuadraticHamiltonianError( 'FermionOperator does not map ' 'to QuadraticHamiltonian (not Hermitian).') else: matching_coefficient = -fermion_operator.terms[ conjugate_term].conjugate() discrepancy = abs(coefficient - matching_coefficient) if discrepancy > EQ_TOLERANCE: raise QuadraticHamiltonianError( 'FermionOperator does not map ' 'to QuadraticHamiltonian (not Hermitian).') antisymmetric_part[p, q] += .5 * coefficient antisymmetric_part[q, p] -= .5 * coefficient else: # ladder_type == [0, 0] # Need to check that the corresponding [1, 1] term is present conjugate_term = ((p, 1), (q, 1)) if conjugate_term not in fermion_operator.terms: raise QuadraticHamiltonianError( 'FermionOperator does not map ' 'to QuadraticHamiltonian (not Hermitian).') else: matching_coefficient = -fermion_operator.terms[ conjugate_term].conjugate() discrepancy = abs(coefficient - matching_coefficient) if discrepancy > EQ_TOLERANCE: raise QuadraticHamiltonianError( 'FermionOperator does not map ' 'to QuadraticHamiltonian (not Hermitian).') antisymmetric_part[p, q] -= .5 * coefficient.conjugate() antisymmetric_part[q, p] += .5 * coefficient.conjugate() else: # Operator contains non-quadratic terms raise QuadraticHamiltonianError('FermionOperator does not map ' 'to QuadraticHamiltonian ' '(contains non-quadratic terms).') # Compute Hermitian part hermitian_part = (combined_hermitian_part + chemical_potential * numpy.eye(n_qubits)) # Check that the operator is Hermitian difference = hermitian_part - hermitian_part.T.conj() discrepancy = numpy.max(numpy.abs(difference)) if discrepancy > EQ_TOLERANCE: raise QuadraticHamiltonianError( 'FermionOperator does not map ' 'to QuadraticHamiltonian (not Hermitian).') # Form QuadraticHamiltonian and return. discrepancy = numpy.max(numpy.abs(antisymmetric_part)) if discrepancy < EQ_TOLERANCE: # Hamiltonian conserves particle number quadratic_hamiltonian = QuadraticHamiltonian( constant, hermitian_part, chemical_potential=chemical_potential) else: # Hamiltonian does not conserve particle number quadratic_hamiltonian = QuadraticHamiltonian(constant, hermitian_part, antisymmetric_part, chemical_potential) return quadratic_hamiltonian
def get_quadratic_hamiltonian(fermion_operator, chemical_potential=0., n_qubits=None, ignore_incompatible_terms=False): r"""Convert a quadratic fermionic operator to QuadraticHamiltonian. Args: fermion_operator(FermionOperator): The operator to convert. chemical_potential(float): A chemical potential to include in the returned operator n_qubits(int): Optionally specify the total number of qubits in the system ignore_incompatible_terms(bool): This flag determines the behavior of this method when it encounters terms that are not quadratic that is, terms that are not of the form a^\dagger_p a_q. If set to True, this method will simply ignore those terms. If False, then this method will raise an error if it encounters such a term. The default setting is False. Returns: quadratic_hamiltonian: An instance of the QuadraticHamiltonian class. Raises: TypeError: Input must be a FermionOperator. TypeError: FermionOperator does not map to QuadraticHamiltonian. Warning: Even assuming that each creation or annihilation operator appears at most a constant number of times in the original operator, the runtime of this method is exponential in the number of qubits. """ if not isinstance(fermion_operator, FermionOperator): raise TypeError('Input must be a FermionOperator.') if n_qubits is None: n_qubits = count_qubits(fermion_operator) if n_qubits < count_qubits(fermion_operator): raise ValueError('Invalid number of qubits specified.') # Normal order the terms and initialize. fermion_operator = normal_ordered(fermion_operator) constant = 0. combined_hermitian_part = numpy.zeros((n_qubits, n_qubits), complex) antisymmetric_part = numpy.zeros((n_qubits, n_qubits), complex) # Loop through terms and assign to matrix. for term in fermion_operator.terms: coefficient = fermion_operator.terms[term] # Ignore this term if the coefficient is zero if abs(coefficient) < EQ_TOLERANCE: continue if len(term) == 0: # Constant term constant = coefficient elif len(term) == 2: ladder_type = [operator[1] for operator in term] p, q = [operator[0] for operator in term] if ladder_type == [1, 0]: combined_hermitian_part[p, q] = coefficient elif ladder_type == [1, 1]: # Need to check that the corresponding [0, 0] term is present conjugate_term = ((p, 0), (q, 0)) if conjugate_term not in fermion_operator.terms: raise QuadraticHamiltonianError( 'FermionOperator does not map ' 'to QuadraticHamiltonian (not Hermitian).') else: matching_coefficient = -fermion_operator.terms[ conjugate_term].conjugate() discrepancy = abs(coefficient - matching_coefficient) if discrepancy > EQ_TOLERANCE: raise QuadraticHamiltonianError( 'FermionOperator does not map ' 'to QuadraticHamiltonian (not Hermitian).') antisymmetric_part[p, q] += .5 * coefficient antisymmetric_part[q, p] -= .5 * coefficient else: # ladder_type == [0, 0] # Need to check that the corresponding [1, 1] term is present conjugate_term = ((p, 1), (q, 1)) if conjugate_term not in fermion_operator.terms: raise QuadraticHamiltonianError( 'FermionOperator does not map ' 'to QuadraticHamiltonian (not Hermitian).') else: matching_coefficient = -fermion_operator.terms[ conjugate_term].conjugate() discrepancy = abs(coefficient - matching_coefficient) if discrepancy > EQ_TOLERANCE: raise QuadraticHamiltonianError( 'FermionOperator does not map ' 'to QuadraticHamiltonian (not Hermitian).') antisymmetric_part[p, q] -= .5 * coefficient.conjugate() antisymmetric_part[q, p] += .5 * coefficient.conjugate() elif not ignore_incompatible_terms: # Operator contains non-quadratic terms raise QuadraticHamiltonianError('FermionOperator does not map ' 'to QuadraticHamiltonian ' '(contains non-quadratic terms).') # Compute Hermitian part hermitian_part = (combined_hermitian_part + chemical_potential * numpy.eye(n_qubits)) # Check that the operator is Hermitian if not is_hermitian(hermitian_part): raise QuadraticHamiltonianError( 'FermionOperator does not map ' 'to QuadraticHamiltonian (not Hermitian).') # Form QuadraticHamiltonian and return. discrepancy = numpy.max(numpy.abs(antisymmetric_part)) if discrepancy < EQ_TOLERANCE: # Hamiltonian conserves particle number quadratic_hamiltonian = QuadraticHamiltonian( hermitian_part, constant=constant, chemical_potential=chemical_potential) else: # Hamiltonian does not conserve particle number quadratic_hamiltonian = QuadraticHamiltonian(hermitian_part, antisymmetric_part, constant, chemical_potential) return quadratic_hamiltonian