-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
255 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
# (C) Copyright IBM 2024. | ||
# | ||
# This code is licensed under the Apache License, Version 2.0. You may | ||
# obtain a copy of this license in the LICENSE.txt file in the root directory | ||
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. | ||
# | ||
# Any modifications or derivative works of this code must retain this | ||
# copyright notice, and modified files need to carry a notice indicating | ||
# that they have been altered from the originals. | ||
|
||
"""Jordan-Wigner transformation.""" | ||
|
||
from __future__ import annotations | ||
|
||
import functools | ||
|
||
from qiskit.quantum_info import SparsePauliOp | ||
|
||
from ffsim.operators import FermionOperator | ||
|
||
|
||
def jordan_wigner(op: FermionOperator, n_qubits: int | None = None) -> SparsePauliOp: | ||
"""Jordan-Wigner transformation. | ||
Transform a fermion operator to a qubit operator using the Jordan-Wigner | ||
transformation. | ||
Args: | ||
op: The fermion operator to transform. | ||
n_qubits: The number of qubits to include in the output qubit operator. If not | ||
specified, the minimum number of qubits needed to accommodate the fermion | ||
operator will be used. Must be non-negative. | ||
Returns: | ||
The qubit operator as a Qiskit SparsePauliOp. | ||
Raises: | ||
ValueError: Number of qubits was negative. | ||
ValueError: Number of qubits was not enough to accommodate the fermion operator. | ||
""" | ||
if n_qubits and n_qubits < 0: | ||
raise ValueError(f"Number of qubits must be non-negative. Got {n_qubits}.") | ||
if not op: | ||
return SparsePauliOp.from_sparse_list([("", [], 0.0)], num_qubits=n_qubits or 0) | ||
|
||
norb = 1 + max(orb for term in op for _, _, orb in term) | ||
if n_qubits is None: | ||
n_qubits = 2 * norb | ||
if n_qubits < 2 * norb: | ||
raise ValueError( | ||
"Number of qubits is not enough to accommodate the fermion operator. " | ||
f"The fermion operator has {norb} spatial orbitals, so at least {2 * norb} " | ||
f"qubits is needed, but got {n_qubits}." | ||
) | ||
|
||
qubit_terms = [SparsePauliOp.from_sparse_list([("", [], 0.0)], num_qubits=n_qubits)] | ||
for term, coeff in op.items(): | ||
qubit_op = SparsePauliOp.from_sparse_list( | ||
[("", [], coeff)], num_qubits=n_qubits | ||
) | ||
for action, spin, orb in term: | ||
qubit_op @= _qubit_action(action, orb + spin * norb, n_qubits) | ||
qubit_terms.append(qubit_op) | ||
|
||
return SparsePauliOp.sum(qubit_terms) | ||
|
||
|
||
@functools.cache | ||
def _qubit_action(action: bool, qubit: int, n_qubits: int): | ||
qubits = list(range(qubit + 1)) | ||
return SparsePauliOp.from_sparse_list( | ||
[ | ||
("Z" * qubit + "X", qubits, 0.5), | ||
("Z" * qubit + "Y", qubits, -0.5j if action else 0.5j), | ||
], | ||
num_qubits=n_qubits, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
# (C) Copyright IBM 2024. | ||
# | ||
# This code is licensed under the Apache License, Version 2.0. You may | ||
# obtain a copy of this license in the LICENSE.txt file in the root directory | ||
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. | ||
# | ||
# Any modifications or derivative works of this code must retain this | ||
# copyright notice, and modified files need to carry a notice indicating | ||
# that they have been altered from the originals. | ||
|
||
"""Tests for Jordan-Wigner transformation.""" | ||
|
||
import numpy as np | ||
import pytest | ||
|
||
import ffsim | ||
import ffsim.random.random | ||
|
||
|
||
@pytest.mark.parametrize("norb, nelec", ffsim.testing.generate_norb_nelec(range(5))) | ||
def test_random(norb: int, nelec: tuple[int, int]): | ||
"""Test on random fermion Hamiltonian.""" | ||
rng = np.random.default_rng(4482) | ||
op = ffsim.random.random_fermion_hamiltonian(norb, seed=rng) | ||
linop = ffsim.linear_operator(op, norb=norb, nelec=nelec) | ||
vec = ffsim.random.random_state_vector(ffsim.dim(norb, nelec), seed=rng) | ||
|
||
qubit_op = ffsim.qiskit.jordan_wigner(op) | ||
qubit_op_sparse = qubit_op.to_matrix(sparse=True) | ||
actual_result = qubit_op_sparse @ ffsim.qiskit.ffsim_vec_to_qiskit_vec( | ||
vec, norb, nelec | ||
) | ||
expected_result = ffsim.qiskit.ffsim_vec_to_qiskit_vec(linop @ vec, norb, nelec) | ||
np.testing.assert_allclose(actual_result, expected_result, atol=1e-12) | ||
|
||
qubit_op = ffsim.qiskit.jordan_wigner(op, n_qubits=2 * norb) | ||
qubit_op_sparse = qubit_op.to_matrix(sparse=True) | ||
actual_result = qubit_op_sparse @ ffsim.qiskit.ffsim_vec_to_qiskit_vec( | ||
vec, norb, nelec | ||
) | ||
np.testing.assert_allclose(actual_result, expected_result, atol=1e-12) | ||
|
||
|
||
def test_bad_qubit_number(): | ||
"""Test passing bad number of qubits raises errors.""" | ||
op = ffsim.FermionOperator({(ffsim.cre_a(3),): 1.0}) | ||
with pytest.raises(ValueError, match="non-negative"): | ||
_ = ffsim.qiskit.jordan_wigner(op, n_qubits=-1) | ||
with pytest.raises(ValueError, match="enough"): | ||
_ = ffsim.qiskit.jordan_wigner(op, n_qubits=7) | ||
|
||
|
||
def test_hubbard(): | ||
"""Test on Hubbard model""" | ||
rng = np.random.default_rng(7431) | ||
norb_x = 2 | ||
norb_y = 2 | ||
norb = norb_x * norb_y | ||
nelec = (norb // 2, norb // 2) | ||
op = ffsim.fermi_hubbard_2d( | ||
norb_x=norb_x, | ||
norb_y=norb_y, | ||
tunneling=rng.uniform(-10, 10), | ||
interaction=rng.uniform(-10, 10), | ||
chemical_potential=rng.uniform(-10, 10), | ||
nearest_neighbor_interaction=rng.uniform(-10, 10), | ||
periodic=False, | ||
) | ||
linop = ffsim.linear_operator(op, norb=norb, nelec=nelec) | ||
vec = ffsim.random.random_state_vector(ffsim.dim(norb, nelec), seed=rng) | ||
qubit_op = ffsim.qiskit.jordan_wigner(op) | ||
qubit_op_sparse = qubit_op.to_matrix(sparse=True) | ||
actual_result = qubit_op_sparse @ ffsim.qiskit.ffsim_vec_to_qiskit_vec( | ||
vec, norb, nelec | ||
) | ||
expected_result = ffsim.qiskit.ffsim_vec_to_qiskit_vec(linop @ vec, norb, nelec) | ||
np.testing.assert_allclose(actual_result, expected_result) |