From 324ecd05ba1d5508b08141f072c849e96815fb71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20P=2E=20Moutinho?= <56390829+jpmoutinho@users.noreply.github.com> Date: Thu, 15 Aug 2024 12:19:24 +0200 Subject: [PATCH 1/5] add more tests --- tests/helpers.py | 13 +++++++-- tests/test_tensor.py | 66 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/tests/helpers.py b/tests/helpers.py index cdb30fe9..1ac3b1d7 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -47,16 +47,25 @@ def calc_mat_vec_wavefunction( ) -def get_op_support(op: type[Primitive] | type[Parametric], n_qubits: int) -> tuple: +def get_op_support( + op: type[Primitive] | type[Parametric], n_qubits: int, get_ordered: bool = False +) -> tuple: """Decides a random qubit support for any gate, up to a some max n_qubits.""" if op in OPS_1Q.union(OPS_PARAM_1Q): supp: tuple = (random.randint(0, n_qubits - 1),) + ordered_supp = supp elif op in OPS_2Q.union(OPS_PARAM_2Q): supp = tuple(random.sample(range(n_qubits), 2)) + ordered_supp = tuple(sorted(supp)) elif op in OPS_3Q: i, j, k = tuple(random.sample(range(n_qubits), 3)) + a, b, c = tuple(sorted((i, j, k))) supp = ((i, j), k) if op == Toffoli else (i, (j, k)) - return supp + ordered_supp = ((a, b), c) if op == Toffoli else (a, (b, c)) + if get_ordered: + return supp, ordered_supp + else: + return supp def random_pauli_hamiltonian( diff --git a/tests/test_tensor.py b/tests/test_tensor.py index 1dbd2b80..d6aa481e 100644 --- a/tests/test_tensor.py +++ b/tests/test_tensor.py @@ -10,17 +10,29 @@ from pyqtorch.hamiltonians import GeneratorType, HamiltonianEvolution from pyqtorch.primitives import ( CNOT, + CPHASE, + CRX, + CRY, + CRZ, + CSWAP, + CY, + CZ, + OPS_2Q, + OPS_3Q, OPS_DIGITAL, OPS_PARAM, + OPS_PARAM_2Q, SWAP, N, Parametric, Primitive, Projector, + Toffoli, ) from pyqtorch.utils import ( ATOL, RTOL, + permute_basis, random_state, ) @@ -240,3 +252,57 @@ def test_hevo_tensor_tensor( psi_star = operator(psi_init, values) psi_expected = calc_mat_vec_wavefunction(operator, psi_init, values, full_support) assert torch.allclose(psi_star, psi_expected, rtol=RTOL, atol=ATOL) + + +@pytest.mark.parametrize("n_qubits", [3, 5]) +def test_permute_tensor(n_qubits: int) -> None: + for op in OPS_2Q.union(OPS_3Q): + supp, ordered_supp = get_op_support(op, n_qubits, get_ordered=True) + + op_concrete1 = op(*supp) + op_concrete2 = op(*ordered_supp) + + mat1 = op_concrete1.tensor() + mat2 = op_concrete2.tensor() + + perm = op_concrete1._qubit_support.qubits + + assert torch.allclose(mat1, permute_basis(mat2, perm)) + assert torch.allclose(mat2, permute_basis(mat1, perm, inv=True)) + + +@pytest.mark.parametrize("n_qubits", [3, 5]) +@pytest.mark.parametrize("batch_size", [1, 5]) +def test_permute_tensor_parametric(n_qubits: int, batch_size: int) -> None: + for op in OPS_PARAM_2Q: + supp, ordered_supp = get_op_support(op, n_qubits, get_ordered=True) + params = [f"{op.__name__}_th{i}" for i in range(op.n_params)] + values = {param: torch.rand(batch_size) for param in params} + + op_concrete1 = op(*supp, *params) + op_concrete2 = op(*ordered_supp, *params) + + mat1 = op_concrete1.tensor(values=values) + mat2 = op_concrete2.tensor(values=values) + + perm = op_concrete1._qubit_support.qubits + + assert torch.allclose(mat1, permute_basis(mat2, perm)) + assert torch.allclose(mat2, permute_basis(mat1, perm, inv=True)) + + +def test_tensor_symmetries() -> None: + assert not torch.allclose(CNOT(0, 1).tensor(), CNOT(1, 0).tensor()) + assert not torch.allclose(CY(0, 1).tensor(), CY(1, 0).tensor()) + assert not torch.allclose(CZ(0, 1).tensor(), CY(1, 0).tensor()) + assert not torch.allclose(CRX(0, 1, 1.0).tensor(), CRX(1, 0, 1.0).tensor()) + assert not torch.allclose(CRY(0, 1, 1.0).tensor(), CRY(1, 0, 1.0).tensor()) + assert not torch.allclose(CRZ(0, 1, 1.0).tensor(), CRZ(1, 0, 1.0).tensor()) + assert torch.allclose(CPHASE(0, 1, 1.0).tensor(), CPHASE(1, 0, 1.0).tensor()) + assert torch.allclose(SWAP(0, 1).tensor(), SWAP(1, 0).tensor()) + assert torch.allclose(CSWAP(0, (1, 2)).tensor(), CSWAP(0, (2, 1)).tensor()) + assert torch.allclose(CSWAP(1, (0, 2)).tensor(), CSWAP(1, (2, 0)).tensor()) # F + assert torch.allclose(CSWAP(2, (0, 1)).tensor(), CSWAP(2, (1, 0)).tensor()) # F + assert torch.allclose(Toffoli((0, 1), 2).tensor(), Toffoli((1, 0), 2).tensor()) + assert torch.allclose(Toffoli((0, 2), 1).tensor(), Toffoli((2, 0), 1).tensor()) # F + assert torch.allclose(Toffoli((1, 2), 0).tensor(), Toffoli((2, 1), 0).tensor()) # F From 13e76b7fe4a31481242170bb8f0fdffb28655eca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20P=2E=20Moutinho?= <56390829+jpmoutinho@users.noreply.github.com> Date: Thu, 15 Aug 2024 14:23:29 +0200 Subject: [PATCH 2/5] fix tests --- pyqtorch/quantum_operation.py | 2 +- tests/test_tensor.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyqtorch/quantum_operation.py b/pyqtorch/quantum_operation.py index b95ffb7b..99fe50c8 100644 --- a/pyqtorch/quantum_operation.py +++ b/pyqtorch/quantum_operation.py @@ -331,7 +331,7 @@ def tensor( """ blockmat = self.operator_function(values, embedding) if self._qubit_support.qubits != self.qubit_support: - blockmat = permute_basis(blockmat, self._qubit_support.qubits) + blockmat = permute_basis(blockmat, self._qubit_support.qubits, inv=True) if full_support is None: return blockmat else: diff --git a/tests/test_tensor.py b/tests/test_tensor.py index d6aa481e..72f19efb 100644 --- a/tests/test_tensor.py +++ b/tests/test_tensor.py @@ -301,8 +301,8 @@ def test_tensor_symmetries() -> None: assert torch.allclose(CPHASE(0, 1, 1.0).tensor(), CPHASE(1, 0, 1.0).tensor()) assert torch.allclose(SWAP(0, 1).tensor(), SWAP(1, 0).tensor()) assert torch.allclose(CSWAP(0, (1, 2)).tensor(), CSWAP(0, (2, 1)).tensor()) - assert torch.allclose(CSWAP(1, (0, 2)).tensor(), CSWAP(1, (2, 0)).tensor()) # F - assert torch.allclose(CSWAP(2, (0, 1)).tensor(), CSWAP(2, (1, 0)).tensor()) # F + assert torch.allclose(CSWAP(1, (0, 2)).tensor(), CSWAP(1, (2, 0)).tensor()) + assert torch.allclose(CSWAP(2, (0, 1)).tensor(), CSWAP(2, (1, 0)).tensor()) assert torch.allclose(Toffoli((0, 1), 2).tensor(), Toffoli((1, 0), 2).tensor()) - assert torch.allclose(Toffoli((0, 2), 1).tensor(), Toffoli((2, 0), 1).tensor()) # F - assert torch.allclose(Toffoli((1, 2), 0).tensor(), Toffoli((2, 1), 0).tensor()) # F + assert torch.allclose(Toffoli((0, 2), 1).tensor(), Toffoli((2, 0), 1).tensor()) + assert torch.allclose(Toffoli((1, 2), 0).tensor(), Toffoli((2, 1), 0).tensor()) From 21c74d8a714e85e38595cbebcbc09db2b6de60f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20P=2E=20Moutinho?= <56390829+jpmoutinho@users.noreply.github.com> Date: Thu, 15 Aug 2024 14:51:21 +0200 Subject: [PATCH 3/5] more permute tests --- tests/test_tensor.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/test_tensor.py b/tests/test_tensor.py index 72f19efb..b96987c5 100644 --- a/tests/test_tensor.py +++ b/tests/test_tensor.py @@ -39,6 +39,7 @@ pi = torch.tensor(torch.pi) +@pytest.mark.parametrize("use_permute", [True, False]) @pytest.mark.parametrize("use_full_support", [True, False]) @pytest.mark.parametrize("n_qubits", [4, 5]) @pytest.mark.parametrize("batch_size", [1, 5]) @@ -267,8 +268,8 @@ def test_permute_tensor(n_qubits: int) -> None: perm = op_concrete1._qubit_support.qubits - assert torch.allclose(mat1, permute_basis(mat2, perm)) - assert torch.allclose(mat2, permute_basis(mat1, perm, inv=True)) + assert torch.allclose(mat1, permute_basis(mat2, perm, inv=True)) + assert torch.allclose(mat2, permute_basis(mat1, perm)) @pytest.mark.parametrize("n_qubits", [3, 5]) @@ -287,8 +288,8 @@ def test_permute_tensor_parametric(n_qubits: int, batch_size: int) -> None: perm = op_concrete1._qubit_support.qubits - assert torch.allclose(mat1, permute_basis(mat2, perm)) - assert torch.allclose(mat2, permute_basis(mat1, perm, inv=True)) + assert torch.allclose(mat1, permute_basis(mat2, perm, inv=True)) + assert torch.allclose(mat2, permute_basis(mat1, perm)) def test_tensor_symmetries() -> None: From 5b9417beeb4f3974d24819b2bed505cb5651ab87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20P=2E=20Moutinho?= <56390829+jpmoutinho@users.noreply.github.com> Date: Thu, 15 Aug 2024 14:56:59 +0200 Subject: [PATCH 4/5] fix test argument --- tests/test_tensor.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_tensor.py b/tests/test_tensor.py index b96987c5..f98ca8ae 100644 --- a/tests/test_tensor.py +++ b/tests/test_tensor.py @@ -43,7 +43,9 @@ @pytest.mark.parametrize("use_full_support", [True, False]) @pytest.mark.parametrize("n_qubits", [4, 5]) @pytest.mark.parametrize("batch_size", [1, 5]) -def test_digital_tensor(n_qubits: int, batch_size: int, use_full_support: bool) -> None: +def test_digital_tensor( + n_qubits: int, batch_size: int, use_full_support: bool, use_permute: bool +) -> None: """ Goes through all non-parametric gates and tests their application to a random state in comparison with the `tensor` method, either using just the qubit support of the gate @@ -57,7 +59,7 @@ def test_digital_tensor(n_qubits: int, batch_size: int, use_full_support: bool) psi_star = op_concrete(psi_init) full_support = tuple(range(n_qubits)) if use_full_support else None psi_expected = calc_mat_vec_wavefunction( - op_concrete, psi_init, full_support=full_support + op_concrete, psi_init, full_support=full_support, use_permute=use_permute ) assert torch.allclose(psi_star, psi_expected, rtol=RTOL, atol=ATOL) From f03bbfcd0ee2f32799a4d29b5feacc7ac005d280 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20P=2E=20Moutinho?= <56390829+jpmoutinho@users.noreply.github.com> Date: Thu, 15 Aug 2024 15:16:22 +0200 Subject: [PATCH 5/5] bump version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 578ad7a3..7fec3a28 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" name = "pyqtorch" description = "An efficient, large-scale emulator designed for quantum machine learning, seamlessly integrated with a PyTorch backend. Please refer to https://pyqtorch.readthedocs.io/en/latest/ for setup and usage info, along with the full documentation." readme = "README.md" -version = "1.4.2" +version = "1.4.3" requires-python = ">=3.8,<3.13" license = { text = "Apache 2.0" } keywords = ["quantum"]