From ed5d2b0fd1957dc114df609e2a2053c68390c9fb Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Thu, 20 Jul 2023 10:53:49 -0500 Subject: [PATCH 01/22] Don't batch unnecessarily --- .../cutting/cutting_evaluation.py | 53 +++++++++++-------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/circuit_knitting/cutting/cutting_evaluation.py b/circuit_knitting/cutting/cutting_evaluation.py index fdfc4c1d0..27159ef9b 100644 --- a/circuit_knitting/cutting/cutting_evaluation.py +++ b/circuit_knitting/cutting/cutting_evaluation.py @@ -129,28 +129,43 @@ def execute_experiments( ) # Create a list of samplers -- one for each qubit partition - num_partitions = len(subexperiments[0]) if isinstance(samplers, BaseSampler): - samplers_by_partition = [samplers] * num_partitions + samplers_by_partition = [samplers] + batches = [ + [ + sample[i] + for sample in subexperiments + for i in range(len(subexperiments[0])) + ] + ] else: samplers_by_partition = [samplers[key] for key in sorted(samplers.keys())] + [ + [sample[i] for sample in subexperiments] + for i in range(len(subexperiments[0])) + ] # Run each partition's sub-experiments - quasi_dists_by_partition = [ + quasi_dists_by_batch = [ _run_experiments_batch( - [sample[i] for sample in subexperiments], + batches[i], samplers_by_partition[i], ) - for i in range(num_partitions) + for i in range(len(samplers_by_partition)) ] # Reformat the counts to match the shape of the input before returning - num_unique_samples = len(subexperiments) quasi_dists: list[list[list[tuple[dict[str, int], int]]]] = [ - [] for _ in range(num_unique_samples) + [] for _ in range(len(subexperiments)) ] - for i in range(num_unique_samples): - for partition in quasi_dists_by_partition: + if len(samplers_by_partition) == 1: + count = 0 + for i in range(len(subexperiments)): + for _ in range(len(subexperiments[0])): + quasi_dists[i].append(quasi_dists_by_batch[0][count]) + count += 1 + for i in range(len(subexperiments)): + for partition in quasi_dists_by_batch: quasi_dists[i].append(partition[i]) return CuttingExperimentResults(quasi_dists, coefficients) @@ -327,18 +342,14 @@ def _run_experiments_batch( quasi_dists_flat = sampler.run(experiments_flat).result().quasi_dists # Reshape the output data to match the input - if len(subexperiments) == 1: - quasi_dists_reshaped = np.array([quasi_dists_flat]) - num_qpd_bits = np.array([num_qpd_bits_flat]) - else: - # We manually build the shape tuple in second arg because it behaves strangely - # with QuantumCircuits in some versions. (e.g. passes local pytest but fails in tox env) - quasi_dists_reshaped = np.reshape( - quasi_dists_flat, (len(subexperiments), len(subexperiments[0])) - ) - num_qpd_bits = np.reshape( - num_qpd_bits_flat, (len(subexperiments), len(subexperiments[0])) - ) + quasi_dists_reshaped = [[] for _ in subexperiments] + num_qpd_bits = [[] for _ in subexperiments] + count = 0 + for i, subcirc in enumerate(subexperiments): + for j in range(len(subcirc)): + quasi_dists_reshaped[i].append(quasi_dists_flat[count]) + num_qpd_bits[i].append(num_qpd_bits_flat[count]) + count += 1 # Create the counts tuples, which include the number of QPD measurement bits quasi_dists: list[list[tuple[dict[str, float], int]]] = [ From c2200d1bcf7dc1ea85ae9c1077e4ed0fe16e73a6 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Thu, 20 Jul 2023 10:57:01 -0500 Subject: [PATCH 02/22] mypy --- circuit_knitting/cutting/cutting_evaluation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/circuit_knitting/cutting/cutting_evaluation.py b/circuit_knitting/cutting/cutting_evaluation.py index 27159ef9b..34d12a882 100644 --- a/circuit_knitting/cutting/cutting_evaluation.py +++ b/circuit_knitting/cutting/cutting_evaluation.py @@ -342,8 +342,8 @@ def _run_experiments_batch( quasi_dists_flat = sampler.run(experiments_flat).result().quasi_dists # Reshape the output data to match the input - quasi_dists_reshaped = [[] for _ in subexperiments] - num_qpd_bits = [[] for _ in subexperiments] + quasi_dists_reshaped: list[list[QuasiDistribution]] = [[] for _ in subexperiments] + num_qpd_bits: list[list[int]] = [[] for _ in subexperiments] count = 0 for i, subcirc in enumerate(subexperiments): for j in range(len(subcirc)): From 30c3bbfc5bfba7b6345b37c1b05177e58179e3a6 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Thu, 20 Jul 2023 12:32:09 -0500 Subject: [PATCH 03/22] black --- circuit_knitting/cutting/cutting_evaluation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circuit_knitting/cutting/cutting_evaluation.py b/circuit_knitting/cutting/cutting_evaluation.py index 34d12a882..ab310840a 100644 --- a/circuit_knitting/cutting/cutting_evaluation.py +++ b/circuit_knitting/cutting/cutting_evaluation.py @@ -342,7 +342,7 @@ def _run_experiments_batch( quasi_dists_flat = sampler.run(experiments_flat).result().quasi_dists # Reshape the output data to match the input - quasi_dists_reshaped: list[list[QuasiDistribution]] = [[] for _ in subexperiments] + quasi_dists_reshaped: list[list[QuasiDistribution]] = [[] for _ in subexperiments] num_qpd_bits: list[list[int]] = [[] for _ in subexperiments] count = 0 for i, subcirc in enumerate(subexperiments): From 3b46b80abc43618123c04fea1cccc27ee750dca4 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Thu, 20 Jul 2023 12:34:23 -0500 Subject: [PATCH 04/22] Missing varname --- circuit_knitting/cutting/cutting_evaluation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circuit_knitting/cutting/cutting_evaluation.py b/circuit_knitting/cutting/cutting_evaluation.py index ab310840a..1e95d525f 100644 --- a/circuit_knitting/cutting/cutting_evaluation.py +++ b/circuit_knitting/cutting/cutting_evaluation.py @@ -140,7 +140,7 @@ def execute_experiments( ] else: samplers_by_partition = [samplers[key] for key in sorted(samplers.keys())] - [ + batches = [ [sample[i] for sample in subexperiments] for i in range(len(subexperiments[0])) ] From dccddc21482e54fa97913b0fa71ac2629c7bf125 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Thu, 20 Jul 2023 12:40:00 -0500 Subject: [PATCH 05/22] fix bug --- circuit_knitting/cutting/cutting_evaluation.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/circuit_knitting/cutting/cutting_evaluation.py b/circuit_knitting/cutting/cutting_evaluation.py index 1e95d525f..ff2d5d024 100644 --- a/circuit_knitting/cutting/cutting_evaluation.py +++ b/circuit_knitting/cutting/cutting_evaluation.py @@ -158,15 +158,19 @@ def execute_experiments( quasi_dists: list[list[list[tuple[dict[str, int], int]]]] = [ [] for _ in range(len(subexperiments)) ] + + # If one sampler was used for all subcircuits, some extra post-processing + # is needed to re-build the output data structure if len(samplers_by_partition) == 1: count = 0 for i in range(len(subexperiments)): for _ in range(len(subexperiments[0])): quasi_dists[i].append(quasi_dists_by_batch[0][count]) count += 1 - for i in range(len(subexperiments)): - for partition in quasi_dists_by_batch: - quasi_dists[i].append(partition[i]) + else: + for i in range(len(subexperiments)): + for partition in quasi_dists_by_batch: + quasi_dists[i].append(partition[i]) return CuttingExperimentResults(quasi_dists, coefficients) From 78e7f6b6dcb2cca302ff5002647fdfe9a111d2db Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Thu, 20 Jul 2023 12:50:41 -0500 Subject: [PATCH 06/22] Clean up code --- circuit_knitting/cutting/cutting_evaluation.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/circuit_knitting/cutting/cutting_evaluation.py b/circuit_knitting/cutting/cutting_evaluation.py index ff2d5d024..0267e2e2c 100644 --- a/circuit_knitting/cutting/cutting_evaluation.py +++ b/circuit_knitting/cutting/cutting_evaluation.py @@ -159,18 +159,15 @@ def execute_experiments( [] for _ in range(len(subexperiments)) ] - # If one sampler was used for all subcircuits, some extra post-processing - # is needed to re-build the output data structure - if len(samplers_by_partition) == 1: - count = 0 - for i in range(len(subexperiments)): - for _ in range(len(subexperiments[0])): + # Re-build the output data structure to match the shape of input subexperiments + count = 0 + for i in range(len(subexperiments)): + for j in range(len(subexperiments[0])): + if len(samplers_by_partition) == 1: quasi_dists[i].append(quasi_dists_by_batch[0][count]) count += 1 - else: - for i in range(len(subexperiments)): - for partition in quasi_dists_by_batch: - quasi_dists[i].append(partition[i]) + else: + quasi_dists[i].append(quasi_dists_by_batch[j][i]) return CuttingExperimentResults(quasi_dists, coefficients) From 55a7c741be64a4f525f96f3353246b23ca537df4 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Thu, 20 Jul 2023 19:32:42 -0500 Subject: [PATCH 07/22] Improve comments --- circuit_knitting/cutting/cutting_evaluation.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/circuit_knitting/cutting/cutting_evaluation.py b/circuit_knitting/cutting/cutting_evaluation.py index 0267e2e2c..4ba255f45 100644 --- a/circuit_knitting/cutting/cutting_evaluation.py +++ b/circuit_knitting/cutting/cutting_evaluation.py @@ -128,7 +128,7 @@ def execute_experiments( num_samples, ) - # Create a list of samplers -- one for each qubit partition + # Create a list of samplers to use -- one for each batch if isinstance(samplers, BaseSampler): samplers_by_partition = [samplers] batches = [ @@ -145,7 +145,7 @@ def execute_experiments( for i in range(len(subexperiments[0])) ] - # Run each partition's sub-experiments + # Run each batch of sub-experiments quasi_dists_by_batch = [ _run_experiments_batch( batches[i], @@ -154,12 +154,10 @@ def execute_experiments( for i in range(len(samplers_by_partition)) ] - # Reformat the counts to match the shape of the input before returning + # Build the output data structure to match the shape of input subexperiments quasi_dists: list[list[list[tuple[dict[str, int], int]]]] = [ [] for _ in range(len(subexperiments)) ] - - # Re-build the output data structure to match the shape of input subexperiments count = 0 for i in range(len(subexperiments)): for j in range(len(subexperiments[0])): From ac96bbd2234d7c6d37b8a3bd35b319de02e183e4 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Fri, 21 Jul 2023 09:37:53 -0500 Subject: [PATCH 08/22] release note --- circuit_knitting/cutting/cutting_evaluation.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/circuit_knitting/cutting/cutting_evaluation.py b/circuit_knitting/cutting/cutting_evaluation.py index 4ba255f45..f96e8158e 100644 --- a/circuit_knitting/cutting/cutting_evaluation.py +++ b/circuit_knitting/cutting/cutting_evaluation.py @@ -145,6 +145,9 @@ def execute_experiments( for i in range(len(subexperiments[0])) ] + # There should be one batch per input sampler + assert len(samplers_by_partition) == len(batches) + # Run each batch of sub-experiments quasi_dists_by_batch = [ _run_experiments_batch( From 206b8c31325005812edfde1c86cfbcf82678809b Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Fri, 21 Jul 2023 09:48:54 -0500 Subject: [PATCH 09/22] release note --- releasenotes/notes/batch-by-sampler-c4ae836df9997b1d.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 releasenotes/notes/batch-by-sampler-c4ae836df9997b1d.yaml diff --git a/releasenotes/notes/batch-by-sampler-c4ae836df9997b1d.yaml b/releasenotes/notes/batch-by-sampler-c4ae836df9997b1d.yaml new file mode 100644 index 000000000..919ca0e4d --- /dev/null +++ b/releasenotes/notes/batch-by-sampler-c4ae836df9997b1d.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - | + :func:`~circuit_knitting.cutting.execute_experiments` will no longer create separate Qiskit Runtime jobs for each subcircuit. + Now, separate jobs will only be created if separate :class:`~qiskit.primitives.BaseSampler` instances are provided for each circuit partition. From 24e59aaab2258728dbfc3abd3340497e6c79b72d Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Fri, 21 Jul 2023 09:49:26 -0500 Subject: [PATCH 10/22] Update batch-by-sampler-c4ae836df9997b1d.yaml --- releasenotes/notes/batch-by-sampler-c4ae836df9997b1d.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/notes/batch-by-sampler-c4ae836df9997b1d.yaml b/releasenotes/notes/batch-by-sampler-c4ae836df9997b1d.yaml index 919ca0e4d..ded9a594e 100644 --- a/releasenotes/notes/batch-by-sampler-c4ae836df9997b1d.yaml +++ b/releasenotes/notes/batch-by-sampler-c4ae836df9997b1d.yaml @@ -1,5 +1,5 @@ --- upgrade: - | - :func:`~circuit_knitting.cutting.execute_experiments` will no longer create separate Qiskit Runtime jobs for each subcircuit. + :func:`~circuit_knitting.cutting.execute_experiments` will no longer create separate Qiskit Runtime jobs for each subcircuit by default. Now, separate jobs will only be created if separate :class:`~qiskit.primitives.BaseSampler` instances are provided for each circuit partition. From 07fbd1fee0f7441f32cfea514301e247f8ee813e Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Fri, 21 Jul 2023 09:50:13 -0500 Subject: [PATCH 11/22] Update batch-by-sampler-c4ae836df9997b1d.yaml --- releasenotes/notes/batch-by-sampler-c4ae836df9997b1d.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/releasenotes/notes/batch-by-sampler-c4ae836df9997b1d.yaml b/releasenotes/notes/batch-by-sampler-c4ae836df9997b1d.yaml index ded9a594e..dd26c0224 100644 --- a/releasenotes/notes/batch-by-sampler-c4ae836df9997b1d.yaml +++ b/releasenotes/notes/batch-by-sampler-c4ae836df9997b1d.yaml @@ -1,5 +1,5 @@ --- upgrade: - | - :func:`~circuit_knitting.cutting.execute_experiments` will no longer create separate Qiskit Runtime jobs for each subcircuit by default. - Now, separate jobs will only be created if separate :class:`~qiskit.primitives.BaseSampler` instances are provided for each circuit partition. + :func:`~circuit_knitting.cutting.execute_experiments` no longer creates separate Qiskit Runtime jobs for each subcircuit by default. + Now, separate jobs are only created if separate :class:`~qiskit.primitives.BaseSampler` instances are provided for each circuit partition. From 7f5263d6563dae19db50fa9d5badd6f0f76caf4d Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Fri, 21 Jul 2023 09:50:45 -0500 Subject: [PATCH 12/22] Update batch-by-sampler-c4ae836df9997b1d.yaml --- releasenotes/notes/batch-by-sampler-c4ae836df9997b1d.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/notes/batch-by-sampler-c4ae836df9997b1d.yaml b/releasenotes/notes/batch-by-sampler-c4ae836df9997b1d.yaml index dd26c0224..8c07054ea 100644 --- a/releasenotes/notes/batch-by-sampler-c4ae836df9997b1d.yaml +++ b/releasenotes/notes/batch-by-sampler-c4ae836df9997b1d.yaml @@ -1,5 +1,5 @@ --- upgrade: - | - :func:`~circuit_knitting.cutting.execute_experiments` no longer creates separate Qiskit Runtime jobs for each subcircuit by default. + :func:`~circuit_knitting.cutting.execute_experiments` no longer creates separate jobs for each subcircuit by default. Now, separate jobs are only created if separate :class:`~qiskit.primitives.BaseSampler` instances are provided for each circuit partition. From dfb7454369b52f767e7107eab880c7b8ba0573d6 Mon Sep 17 00:00:00 2001 From: Jim Garrison Date: Wed, 19 Jul 2023 17:58:42 -0400 Subject: [PATCH 13/22] Bump Python version in Dockerfile to 3.11 (#331) Now that CKT supports Python 3.11, we might as well use the latest version in the Dockerfile. --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index c8a527841..430379d05 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM jupyter/minimal-notebook:python-3.10 +FROM jupyter/minimal-notebook:python-3.11 LABEL maintainer="Jim Garrison " From b2ac702ec1cbf1d4bd11d4268a75647e5e09fb1d Mon Sep 17 00:00:00 2001 From: Jim Garrison Date: Wed, 19 Jul 2023 20:49:11 -0400 Subject: [PATCH 14/22] Fix formatting of example in `reduce_bitstrings` docstring (#332) --- circuit_knitting/utils/orbital_reduction.py | 1 + 1 file changed, 1 insertion(+) diff --git a/circuit_knitting/utils/orbital_reduction.py b/circuit_knitting/utils/orbital_reduction.py index 37409f8f6..777dcb014 100644 --- a/circuit_knitting/utils/orbital_reduction.py +++ b/circuit_knitting/utils/orbital_reduction.py @@ -31,6 +31,7 @@ def reduce_bitstrings(bitstrings, orbitals_to_reduce): elements of the bitstrings. Example: + >>> reduce_bitstrings([[1, 0, 0, 1, 0], [1, 0, 0, 0, 1], [1, 1, 1, 1, 0]], [0, 1]) [(0, 1, 0), (0, 0, 1), (1, 1, 0)] From 5ee8b0080ba7efb32056c2c9229a6ff46e88335e Mon Sep 17 00:00:00 2001 From: Jim Garrison Date: Tue, 25 Jul 2023 21:43:16 -0400 Subject: [PATCH 15/22] Implement cutting of general 2-qubit unitaries (#302) * Add support for `SwapGate` * Reorder terms [ci skip] * Add missing terms * DRY the coefficients * Fix coverage * Add support for `iSwapGate` * Fix black * Add to release note * Fix type hint * Gates without parameters are nicer to work with and can be singletons, one day! * Remove a line * Add comments describing channels * `_copy_unique_sublists` * Add `DCXGate` * Tweak * Implement cutting of general 2-qubit unitaries Builds on #294. Closes #186. * Add tests of additional gates * Fix type annotation * Add explanatory comments * `supported_gates()` -> `explicitly_supported_gates()` * Add to references * Improved error message and test about `to_matrix` conversion failing * Add xref to `QPDBasis` in docstrings * Add `qpdbasis_from_gate` to Sphinx build * Make `explicitly_supported_gates` private and remove its release note It's not clear that this function remains useful now that we support essentially all 2-qubit gates. If we find a use for it in the future, we can re-introduce it (or something like it) as a public interface. * Fix intersphinx link * Release note * Update qpd.py: remove extraneous `from None` --- README.md | 12 +- circuit_knitting/cutting/__init__.py | 2 +- circuit_knitting/cutting/qpd/__init__.py | 2 - circuit_knitting/cutting/qpd/qpd.py | 111 ++++++++++++++---- circuit_knitting/cutting/qpd/qpd_basis.py | 2 +- docs/circuit_cutting/explanation/index.rst | 11 +- .../additional-gates-f4ed6c0e8dc3a9be.yaml | 9 +- .../supported-gates-d2156f58bc07fc7a.yaml | 4 - test/cutting/qpd/test_qpd.py | 25 +++- test/cutting/qpd/test_qpd_basis.py | 29 +++-- test/cutting/test_cutting_roundtrip.py | 7 ++ 11 files changed, 160 insertions(+), 54 deletions(-) delete mode 100644 releasenotes/notes/supported-gates-d2156f58bc07fc7a.yaml diff --git a/README.md b/README.md index 2c4ce3eb4..33c7f1896 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ The toolbox enables users to run parallelized and hybrid (quantum + classical) w The toolbox currently contains the following tools: - Entanglement Forging [[1]](#references) -- Circuit Cutting [[2-6]](#references) +- Circuit Cutting [[2-7]](#references) ---------------------------------------------------------------------------------------------------- @@ -70,13 +70,15 @@ This project is meant to evolve rapidly and, as such, does not follow [Qiskit's [2] Kosuke Mitarai, Keisuke Fujii, [Constructing a virtual two-qubit gate by sampling single-qubit operations](https://iopscience.iop.org/article/10.1088/1367-2630/abd7bc), New J. Phys. 23 023021. -[3] Christophe Piveteau, David Sutter, [Circuit knitting with classical communication](https://arxiv.org/abs/2205.00016), arXiv:2205.00016 [quant-ph]. +[3] Kosuke Mitarai, Keisuke Fujii, [Overhead for simulating a non-local channel with local channels by quasiprobability sampling](https://quantum-journal.org/papers/q-2021-01-28-388/), Quantum 5, 388 (2021). -[4] Lukas Brenner, Christophe Piveteau, David Sutter, [Optimal wire cutting with classical communication](https://arxiv.org/abs/2302.03366), arXiv:2302.03366 [quant-ph]. +[4] Christophe Piveteau, David Sutter, [Circuit knitting with classical communication](https://arxiv.org/abs/2205.00016), arXiv:2205.00016 [quant-ph]. -[5] Wei Tang, Teague Tomesh, Martin Suchara, Jeffrey Larson, Margaret Martonosi, [CutQC: Using small quantum computers for large quantum circuit evaluations](https://doi.org/10.1145/3445814.3446758), Proceedings of the 26th ACM International Conference on Architectural Support for Programming Languages and Operating Systems. pp. 473 (2021). +[5] Lukas Brenner, Christophe Piveteau, David Sutter, [Optimal wire cutting with classical communication](https://arxiv.org/abs/2302.03366), arXiv:2302.03366 [quant-ph]. + +[6] Wei Tang, Teague Tomesh, Martin Suchara, Jeffrey Larson, Margaret Martonosi, [CutQC: Using small quantum computers for large quantum circuit evaluations](https://doi.org/10.1145/3445814.3446758), Proceedings of the 26th ACM International Conference on Architectural Support for Programming Languages and Operating Systems. pp. 473 (2021). -[6] K. Temme, S. Bravyi, and J. M. Gambetta, [Error mitigation for short-depth quantum circuits](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.119.180509), Physical Review Letters, 119(18), (2017). +[7] K. Temme, S. Bravyi, and J. M. Gambetta, [Error mitigation for short-depth quantum circuits](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.119.180509), Physical Review Letters, 119(18), (2017). ---------------------------------------------------------------------------------------------------- diff --git a/circuit_knitting/cutting/__init__.py b/circuit_knitting/cutting/__init__.py index e0c294b39..8aeeda34d 100644 --- a/circuit_knitting/cutting/__init__.py +++ b/circuit_knitting/cutting/__init__.py @@ -57,7 +57,7 @@ qpd.generate_qpd_weights qpd.generate_qpd_samples qpd.decompose_qpd_instructions - qpd.supported_gates + qpd.qpdbasis_from_gate CutQC ===== diff --git a/circuit_knitting/cutting/qpd/__init__.py b/circuit_knitting/cutting/qpd/__init__.py index b734ae205..df68ff7a7 100644 --- a/circuit_knitting/cutting/qpd/__init__.py +++ b/circuit_knitting/cutting/qpd/__init__.py @@ -18,7 +18,6 @@ decompose_qpd_instructions, WeightType, qpdbasis_from_gate, - supported_gates, ) from .instructions import ( BaseQPDGate, @@ -32,7 +31,6 @@ "generate_qpd_weights", "generate_qpd_samples", "decompose_qpd_instructions", - "supported_gates", "QPDBasis", "BaseQPDGate", "TwoQubitQPDGate", diff --git a/circuit_knitting/cutting/qpd/qpd.py b/circuit_knitting/cutting/qpd/qpd.py index 8ec377b0f..172e09fa7 100644 --- a/circuit_knitting/cutting/qpd/qpd.py +++ b/circuit_knitting/cutting/qpd/qpd.py @@ -62,6 +62,8 @@ iSwapGate, DCXGate, ) +from qiskit.extensions import UnitaryGate +from qiskit.quantum_info.synthesis.two_qubit_decompose import TwoQubitWeylDecomposition from qiskit.utils import deprecate_func from .qpd_basis import QPDBasis @@ -555,47 +557,56 @@ def g(f): def qpdbasis_from_gate(gate: Gate) -> QPDBasis: """ - Generate a QPDBasis object, given a supported operation. - - This method currently supports the following operations: - - :class:`~qiskit.circuit.library.RXXGate` - - :class:`~qiskit.circuit.library.RYYGate` - - :class:`~qiskit.circuit.library.RZZGate` - - :class:`~qiskit.circuit.library.CRXGate` - - :class:`~qiskit.circuit.library.CRYGate` - - :class:`~qiskit.circuit.library.CRZGate` - - :class:`~qiskit.circuit.library.CXGate` - - :class:`~qiskit.circuit.library.CYGate` - - :class:`~qiskit.circuit.library.CZGate` - - :class:`~qiskit.circuit.library.CHGate` - - :class:`~qiskit.circuit.library.CSXGate` - - :class:`~qiskit.circuit.library.CSGate` - - :class:`~qiskit.circuit.library.CSdgGate` - - :class:`~qiskit.circuit.library.CPhaseGate` - - :class:`~qiskit.circuit.library.SwapGate` - - :class:`~qiskit.circuit.library.iSwapGate` - - :class:`~qiskit.circuit.library.DCXGate` - - The above gate names can also be determined by calling - :func:`supported_gates`. + Generate a :class:`.QPDBasis` object, given a supported operation. + + All two-qubit gates which implement the :meth:`~qiskit.circuit.Gate.to_matrix` method are + supported. This should include the vast majority of gates with no unbound + parameters, but there are some special cases (see, e.g., `qiskit issue #10396 + `__). Returns: The newly-instantiated :class:`QPDBasis` object Raises: + ValueError: Gate not supported. ValueError: Cannot decompose gate with unbound parameters. + ValueError: ``to_matrix`` conversion of two-qubit gate failed. """ try: f = _qpdbasis_from_gate_funcs[gate.name] except KeyError: - raise ValueError(f"Gate not supported: {gate.name}") from None + pass else: return f(gate) + if isinstance(gate, Gate) and gate.num_qubits == 2: + try: + mat = gate.to_matrix() + except Exception as ex: + raise ValueError( + f"`to_matrix` conversion of two-qubit gate ({gate.name}) failed. " + "Often, this can be caused by unbound parameters." + ) from ex + d = TwoQubitWeylDecomposition(mat) + u = _u_from_thetavec([d.a, d.b, d.c]) + retval = _nonlocal_qpd_basis_from_u(u) + for operations in unique_by_id(m[0] for m in retval.maps): + operations.insert(0, UnitaryGate(d.K2r)) + operations.append(UnitaryGate(d.K1r)) + for operations in unique_by_id(m[1] for m in retval.maps): + operations.insert(0, UnitaryGate(d.K2l)) + operations.append(UnitaryGate(d.K1l)) + return retval + + raise ValueError(f"Gate not supported: {gate.name}") + -def supported_gates() -> set[str]: +def _explicitly_supported_gates() -> set[str]: """ - Return a set of gate names supported for automatic decomposition. + Return a set of instruction names with explicit support for automatic decomposition. + + These instructions are *explicitly* supported by :func:`qpdbasis_from_gate`. + Other instructions may be supported too, via a KAK decomposition. Returns: Set of gate names supported for automatic decomposition. @@ -619,6 +630,54 @@ def _copy_unique_sublists(lsts: tuple[list, ...], /) -> tuple[list, ...]: return tuple(copy_by_id[id(lst)] for lst in lsts) +def _u_from_thetavec( + theta: np.typing.NDArray[np.float64] | Sequence[float], / +) -> np.typing.NDArray[np.complex128]: + r""" + Exponentiate the non-local portion of a KAK decomposition. + + This implements Eq. (6) of https://arxiv.org/abs/2006.11174v2: + + .. math:: + + \exp [ i ( \sum_\alpha^3 \theta_\alpha \, \sigma_\alpha \otimes \sigma_\alpha ) ] + = + \sum_{\alpha=0}^3 u_\alpha \, \sigma_\alpha \otimes \sigma_\alpha + + where each :math:`\theta_\alpha` is assumed to be real, and + :math:`u_\alpha` is complex in general. + """ + theta = np.asarray(theta) + if theta.shape != (3,): + raise ValueError( + f"theta vector has wrong shape: {theta.shape} (1D vector of length 3 expected)" + ) + # First, we note that if we choose the basis vectors II, XX, YY, and ZZ, + # then the following matrix represents one application of the summation in + # the exponential: + # + # 0 θx θy θz + # θx 0 -θz -θy + # θy -θz 0 -θx + # θz -θy -θx 0 + # + # This matrix is symmetric and can be exponentiated by diagonalizing it. + # Its eigendecomposition is given by: + eigvals = np.array( + [ + -np.sum(theta), + -theta[0] + theta[1] + theta[2], + -theta[1] + theta[2] + theta[0], + -theta[2] + theta[0] + theta[1], + ] + ) + eigvecs = np.ones([1, 1]) / 2 - np.eye(4) + # Finally, we exponentiate the eigenvalues of the matrix in diagonal form. + # We also project to the vector [1,0,0,0] on the right, since the + # multiplicative identity is given by II. + return np.transpose(eigvecs) @ (np.exp(1j * eigvals) * eigvecs[:, 0]) + + def _nonlocal_qpd_basis_from_u( u: np.typing.NDArray[np.complex128] | Sequence[complex], / ) -> QPDBasis: diff --git a/circuit_knitting/cutting/qpd/qpd_basis.py b/circuit_knitting/cutting/qpd/qpd_basis.py index a26f95eee..af99e7dd4 100644 --- a/circuit_knitting/cutting/qpd/qpd_basis.py +++ b/circuit_knitting/cutting/qpd/qpd_basis.py @@ -116,7 +116,7 @@ def overhead(self) -> float: @staticmethod def from_gate(gate: Gate) -> "QPDBasis": """ - Generate a QPDBasis object, given a supported operation. + Generate a :class:`.QPDBasis` object, given a supported operation. This static method is provided for convenience; it simply calls :func:`~qpd.qpd.qpdbasis_to_gate` under the hood. diff --git a/docs/circuit_cutting/explanation/index.rst b/docs/circuit_cutting/explanation/index.rst index e1371cc75..7e0566f05 100644 --- a/docs/circuit_cutting/explanation/index.rst +++ b/docs/circuit_cutting/explanation/index.rst @@ -18,9 +18,9 @@ There are two types of cuts: gate cuts and wire cuts. Gate cuts, also known as There are three settings to consider for circuit cutting. The first is where only local operations (LO) [i.e., local *quantum* operations] are available. The other settings introduce classical communication between the circuit executions, which is known in the quantum information literature as LOCC, for `local operations and classical communication `__. The LOCC can be either near-time, one-directional communication between the circuit executions (the second setting), or real-time, bi-directional communication (the third setting). -As mentioned above, the cost of any simulation based on quasiprobability distribution is an exponential sampling overhead. The overhead of a cut gate depends on which gate is cut; see the final appendix of [`1 `__] for details. Here, we will focus on the CNOT gate. If no real-time classical communication is available between qubits of the cut gate or wire, cut CNOT gates incur a sampling overhead of O(:math:`9^n`), and wire cuts incur a sampling overhead of O(:math:`16^n`), where :math:`n` is the total number of cuts. If real-time communication is available (i.e., if the hardware supports “dynamic circuits”), the sampling overhead for both CNOT gate and wire cuts may be reduced to O(:math:`4^n`) [`1 `__,\ `3 `__]; however, support for circuit cutting with classical communication (LOCC) is not yet supported in CKT. +As mentioned above, the cost of any simulation based on quasiprobability distribution is an exponential sampling overhead. The overhead of a cut gate depends on which gate is cut; see the final appendix of [`1 `__] for details. Here, we will focus on the CNOT gate. If no real-time classical communication is available between qubits of the cut gate or wire, cut CNOT gates incur a sampling overhead of O(:math:`9^n`), and wire cuts incur a sampling overhead of O(:math:`16^n`), where :math:`n` is the total number of cuts. If real-time communication is available (i.e., if the hardware supports “dynamic circuits”), the sampling overhead for both CNOT gate and wire cuts may be reduced to O(:math:`4^n`) [`1 `__,\ `4 `__]; however, support for circuit cutting with classical communication (LOCC) is not yet supported in CKT. -For more detailed information on the quasiprobability decomposition technique, refer to the paper, Error mitigation for short-depth quantum circuits [`4 `__]. +For more detailed information on the quasiprobability decomposition technique, refer to the paper, Error mitigation for short-depth quantum circuits [`5 `__]. Key terms ----------------- @@ -48,9 +48,12 @@ https://arxiv.org/abs/2205.00016 single-qubit operations*, https://arxiv.org/abs/1909.07534 -[3] Lukas Brenner, Christophe Piveteau, David Sutter, *Optimal wire cutting with +[3] Kosuke Mitarai, Keisuke Fujii, *Overhead for simulating a non-local channel with local channels by quasiprobability sampling*, +https://arxiv.org/abs/2006.11174 + +[4] Lukas Brenner, Christophe Piveteau, David Sutter, *Optimal wire cutting with classical communication*, https://arxiv.org/abs/2302.03366 -[4] K. Temme, S. Bravyi, and J. M. Gambetta, *Error mitigation for short-depth quantum circuits*, +[5] K. Temme, S. Bravyi, and J. M. Gambetta, *Error mitigation for short-depth quantum circuits*, https://arxiv.org/abs/1612.02058 diff --git a/releasenotes/notes/additional-gates-f4ed6c0e8dc3a9be.yaml b/releasenotes/notes/additional-gates-f4ed6c0e8dc3a9be.yaml index 77a728324..7a6ffec80 100644 --- a/releasenotes/notes/additional-gates-f4ed6c0e8dc3a9be.yaml +++ b/releasenotes/notes/additional-gates-f4ed6c0e8dc3a9be.yaml @@ -1,7 +1,14 @@ --- features: - | - This release adds support for additional cut gates: + The circuit cutting module now supports the cutting of arbitrary + two-qubit gates. This is supported via a KAK decomposition, using + Qiskit's :class:`.TwoQubitWeylDecomposition`, following the method + in `arXiv:2006.11174 `__. + + Additionally, this release adds *explicit* support (i.e., without + relying on a KAK decomposition) for the following gates: + - :class:`~qiskit.circuit.library.CHGate` - :class:`~qiskit.circuit.library.CYGate` - :class:`~qiskit.circuit.library.CSGate` diff --git a/releasenotes/notes/supported-gates-d2156f58bc07fc7a.yaml b/releasenotes/notes/supported-gates-d2156f58bc07fc7a.yaml deleted file mode 100644 index dcb6ba4d5..000000000 --- a/releasenotes/notes/supported-gates-d2156f58bc07fc7a.yaml +++ /dev/null @@ -1,4 +0,0 @@ ---- -upgrade: - - | - Addition of :func:`~circuit_knitting.cutting.qpd.supported_gates` function, which returns the names of all gates which may be automatically decomposed using :func:`~circuit_knitting.cutting.qpd.qpdbasis_from_gate`. diff --git a/test/cutting/qpd/test_qpd.py b/test/cutting/qpd/test_qpd.py index 8b2e86883..6c1bb66de 100644 --- a/test/cutting/qpd/test_qpd.py +++ b/test/cutting/qpd/test_qpd.py @@ -31,6 +31,7 @@ RXXGate, RYYGate, RZZGate, + RZXGate, ) from circuit_knitting.utils.iteration import unique_by_eq @@ -45,6 +46,8 @@ _generate_qpd_weights, _generate_exact_weights_and_conditional_probabilities, _nonlocal_qpd_basis_from_u, + _u_from_thetavec, + _explicitly_supported_gates, ) @@ -256,6 +259,7 @@ def test_decompose_qpd_instructions(self): (RXXGate(np.pi / 7), 1 + 2 * np.abs(np.sin(np.pi / 7))), (RYYGate(np.pi / 7), 1 + 2 * np.abs(np.sin(np.pi / 7))), (RZZGate(np.pi / 7), 1 + 2 * np.abs(np.sin(np.pi / 7))), + (RZXGate(np.pi / 7), 1 + 2 * np.abs(np.sin(np.pi / 7))), (CPhaseGate(np.pi / 7), 1 + 2 * np.abs(np.sin(np.pi / 14))), (CSGate(), 1 + np.sqrt(2)), (CSdgGate(), 1 + np.sqrt(2)), @@ -422,8 +426,8 @@ def from_theta(theta): ) assert weights[map_ids][1] == WeightType.SAMPLED - def test_supported_gates(self): - gates = supported_gates() + def test_explicitly_supported_gates(self): + gates = _explicitly_supported_gates() self.assertEqual( { "rxx", @@ -456,3 +460,20 @@ def test_nonlocal_qpd_basis_from_u(self): e_info.value.args[0] == "u vector has wrong shape: (3,) (1D vector of length 4 expected)" ) + + @data( + ([np.pi / 4] * 3, [(1 + 1j) / np.sqrt(8)] * 4), + ([np.pi / 4, np.pi / 4, 0], [0.5, 0.5j, 0.5j, 0.5]), + ) + @unpack + def test_u_from_thetavec(self, theta, expected): + assert _u_from_thetavec(theta) == pytest.approx(expected) + + def test_u_from_thetavec_exceptions(self): + with self.subTest("Invalid shape"): + with pytest.raises(ValueError) as e_info: + _u_from_thetavec([0, 1, 2, 3]) + assert ( + e_info.value.args[0] + == "theta vector has wrong shape: (4,) (1D vector of length 3 expected)" + ) diff --git a/test/cutting/qpd/test_qpd_basis.py b/test/cutting/qpd/test_qpd_basis.py index 3dbbc3976..eb67ed453 100644 --- a/test/cutting/qpd/test_qpd_basis.py +++ b/test/cutting/qpd/test_qpd_basis.py @@ -120,16 +120,29 @@ def test_eq(self): def test_unsupported_gate(self): with pytest.raises(ValueError) as e_info: - QPDBasis.from_gate(XXMinusYYGate(0.1)) - assert e_info.value.args[0] == "Gate not supported: xx_minus_yy" + QPDBasis.from_gate(C3XGate()) + assert e_info.value.args[0] == "Gate not supported: mcx" def test_unbound_parameter(self): - with pytest.raises(ValueError) as e_info: - QPDBasis.from_gate(RZZGate(Parameter("θ"))) - assert ( - e_info.value.args[0] - == "Cannot decompose (rzz) gate with unbound parameters." - ) + with self.subTest("Explicitly supported gate"): + # For explicitly support gates, we can give a specific error + # message due to unbound parameters. + with pytest.raises(ValueError) as e_info: + QPDBasis.from_gate(RZZGate(Parameter("θ"))) + assert ( + e_info.value.args[0] + == "Cannot decompose (rzz) gate with unbound parameters." + ) + with self.subTest("Implicitly supported gate"): + # For implicitly supported gates, we can detect that `to_matrix` + # failed, but there are other possible explanations, too. See + # https://github.com/Qiskit/qiskit-terra/issues/10396 + with pytest.raises(ValueError) as e_info: + QPDBasis.from_gate(XXPlusYYGate(Parameter("θ"))) + assert ( + e_info.value.args[0] + == "`to_matrix` conversion of two-qubit gate (xx_plus_yy) failed. Often, this can be caused by unbound parameters." + ) def test_erroneous_compare(self): rxx_truth = QPDBasis(self.truth_rxx_maps, self.truth_rxx_coeffs) diff --git a/test/cutting/test_cutting_roundtrip.py b/test/cutting/test_cutting_roundtrip.py index 98e6067f7..0004e2a99 100644 --- a/test/cutting/test_cutting_roundtrip.py +++ b/test/cutting/test_cutting_roundtrip.py @@ -21,6 +21,9 @@ RXXGate, RYYGate, RZZGate, + RZXGate, + XXPlusYYGate, + XXMinusYYGate, CHGate, CXGate, CYGate, @@ -83,6 +86,10 @@ def append_random_unitary(circuit: QuantumCircuit, qubits): [RXXGate(np.pi / 3), CRYGate(np.pi / 7)], [CPhaseGate(np.pi / 3)], [RXXGate(np.pi / 3), CPhaseGate(np.pi / 7)], + [UnitaryGate(random_unitary(2**2))], + [RZXGate(np.pi / 5)], + [XXPlusYYGate(7 * np.pi / 11)], + [XXMinusYYGate(11 * np.pi / 17)], ] ) def example_circuit( From cc758095fb7e267a9574faa068c19fcfa9e5b368 Mon Sep 17 00:00:00 2001 From: Jim Garrison Date: Wed, 26 Jul 2023 21:20:49 -0400 Subject: [PATCH 16/22] Improve the instructions regarding pandoc (#336) I've also tried to make the developer documentation easier to discover --- docs/index.rst | 5 +++++ test/README.md | 8 ++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 135741bb2..64cc7c0c1 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -27,6 +27,11 @@ If you use the Circuit Knitting Toolbox in your research, please cite it accordi .. literalinclude:: ../CITATION.bib :language: bibtex +Developer guide +--------------- + +The developer guide is located at `CONTRIBUTING.md `__ in the root of this project's repository. + Contents -------- diff --git a/test/README.md b/test/README.md index fae561e03..1d6f4d028 100644 --- a/test/README.md +++ b/test/README.md @@ -61,14 +61,18 @@ $ tox -e coverage ## Documentation environment -The `docs` environment builds the [Sphinx] documentation locally. If the build succeeds, it can be viewed by navigating to `docs/_build/html/index.html` in a web browser. +The `docs` environment builds the [Sphinx] documentation locally. -To run: +For the documentation build to succeed, [pandoc](https://pandoc.org/) must be installed. Pandoc is not available via pip, so must be installed through some other means. Linux users are encouraged to install it through their package manager (e.g., `sudo apt-get install -y pandoc`), while macOS users are encouraged to install it via [Homebrew](https://brew.sh/) (`brew install pandoc`). Full instructions are available on [pandoc's installation page](https://pandoc.org/installing.html). + +To run this environment: ```sh $ tox -e docs ``` +If the build succeeds, it can be viewed by navigating to `docs/_build/html/index.html` in a web browser. Specifically, run `pwd` in the same terminal in which you built the docs, then copy that location and paste it in your web browser. You should see a directory listing there. If not, prepend `file://` to the path and try again. Once you see the directory listing, click on `docs`, then `_build`, then `html`, and then finally `index.html` to view the documentation. + [tox]: https://github.com/tox-dev/tox [`tox.ini`]: ../tox.ini [mypy]: https://mypy.readthedocs.io/en/stable/ From 801a6a385a5e0c1f86123c6cf07aa268663d3472 Mon Sep 17 00:00:00 2001 From: Jim Garrison Date: Thu, 27 Jul 2023 18:40:12 -0400 Subject: [PATCH 17/22] Make the repository link more obvious from the Sphinx build (#338) * Make the repository link more obvious from the Sphinx build * Enable "edit" link in the header * Add comment --- docs/conf.py | 18 ++++++++++++++++++ docs/index.rst | 4 ++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 91ccd1838..269bf31f8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -61,6 +61,24 @@ html_theme = "qiskit-ecosystem" html_title = f"{project} {release}" +html_theme_options = { + "footer_icons": [ + # https://pradyunsg.me/furo/customisation/footer/#using-embedded-svgs + { + "name": "GitHub", + "url": "https://github.com/Qiskit-Extensions/circuit-knitting-toolbox", + "html": """ + + + + """, + "class": "", + }, + ], + "source_repository": "https://github.com/Qiskit-Extensions/circuit-knitting-toolbox/", + "source_branch": "main", + "source_directory": "docs/", +} # autodoc/autosummary options autosummary_generate = True diff --git a/docs/index.rst b/docs/index.rst index 64cc7c0c1..1e88480fd 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -11,8 +11,6 @@ The toolbox currently contains the following tools: - Circuit Cutting - Entanglement Forging -The source code to the toolbox is available `on GitHub `_. - .. note:: The `Quantum Serverless `_ framework is documented separately, as it lives in its own repository. Check out `Entanglement Forging Tutorial 2: Forging with Quantum Serverless <./entanglement_forging/tutorials/tutorial_2_forging_with_quantum_serverless.ipynb>`_ and `CutQC Tutorial 3: Circuit Cutting with Quantum Serverless <./circuit_cutting/tutorials/cutqc/tutorial_3_cutting_with_quantum_serverless.ipynb>`_ for examples on how to integrate Quantum Serverless into circuit knitting workflows. @@ -30,6 +28,8 @@ If you use the Circuit Knitting Toolbox in your research, please cite it accordi Developer guide --------------- +The source code to the toolbox is available `on GitHub `__. + The developer guide is located at `CONTRIBUTING.md `__ in the root of this project's repository. Contents From 16d37996cbda26a801481e0a9223cefdf27bad86 Mon Sep 17 00:00:00 2001 From: Jim Garrison Date: Thu, 27 Jul 2023 20:48:04 -0400 Subject: [PATCH 18/22] Add README badge linking to stable documentation (#339) * Add docs badge to link to stable docs * Add ruff badge * Remove ruff --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 33c7f1896..1311b97b9 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ [![Qiskit](https://img.shields.io/badge/Qiskit-%E2%89%A5%200.43.0-6133BD)](https://github.com/Qiskit/qiskit) [![Qiskit Nature](https://img.shields.io/badge/Qiskit%20Nature-%E2%89%A5%200.6.0-6133BD)](https://github.com/Qiskit/qiskit-nature)
+ [![Docs (stable)](https://img.shields.io/badge/Docs-stable-blue.svg)](https://qiskit-extensions.github.io/circuit-knitting-toolbox/) [![DOI](https://zenodo.org/badge/543181258.svg)](https://zenodo.org/badge/latestdoi/543181258) [![License](https://img.shields.io/github/license/Qiskit-Extensions/circuit-knitting-toolbox?label=License)](LICENSE.txt) [![Code style: Black](https://img.shields.io/badge/Code%20style-Black-000.svg)](https://github.com/psf/black) From ac44f843a2ddc82f05605d497ff7f6cb8cd2f2df Mon Sep 17 00:00:00 2001 From: Ibrahim Shehzad <75153717+IbrahimShehzad@users.noreply.github.com> Date: Thu, 27 Jul 2023 23:05:47 -0400 Subject: [PATCH 19/22] Update README.md (#340) Making directions on opening the docs a little more succinct. --- test/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/README.md b/test/README.md index 1d6f4d028..8cf7e9cff 100644 --- a/test/README.md +++ b/test/README.md @@ -71,7 +71,7 @@ To run this environment: $ tox -e docs ``` -If the build succeeds, it can be viewed by navigating to `docs/_build/html/index.html` in a web browser. Specifically, run `pwd` in the same terminal in which you built the docs, then copy that location and paste it in your web browser. You should see a directory listing there. If not, prepend `file://` to the path and try again. Once you see the directory listing, click on `docs`, then `_build`, then `html`, and then finally `index.html` to view the documentation. +If the build succeeds, it can be viewed by navigating to `docs/_build/html/index.html` in a web browser. [tox]: https://github.com/tox-dev/tox [`tox.ini`]: ../tox.ini From 199000fa38d079bf2f21757f62d75496cf301b16 Mon Sep 17 00:00:00 2001 From: Caleb Johnson Date: Fri, 28 Jul 2023 11:03:30 -0500 Subject: [PATCH 20/22] Change var name --- circuit_knitting/cutting/cutting_evaluation.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/circuit_knitting/cutting/cutting_evaluation.py b/circuit_knitting/cutting/cutting_evaluation.py index f96e8158e..2f0a5cdf9 100644 --- a/circuit_knitting/cutting/cutting_evaluation.py +++ b/circuit_knitting/cutting/cutting_evaluation.py @@ -130,7 +130,7 @@ def execute_experiments( # Create a list of samplers to use -- one for each batch if isinstance(samplers, BaseSampler): - samplers_by_partition = [samplers] + samplers_by_batch = [samplers] batches = [ [ sample[i] @@ -139,22 +139,22 @@ def execute_experiments( ] ] else: - samplers_by_partition = [samplers[key] for key in sorted(samplers.keys())] + samplers_by_batch = [samplers[key] for key in sorted(samplers.keys())] batches = [ [sample[i] for sample in subexperiments] for i in range(len(subexperiments[0])) ] # There should be one batch per input sampler - assert len(samplers_by_partition) == len(batches) + assert len(samplers_by_batch) == len(batches) # Run each batch of sub-experiments quasi_dists_by_batch = [ _run_experiments_batch( batches[i], - samplers_by_partition[i], + samplers_by_batch[i], ) - for i in range(len(samplers_by_partition)) + for i in range(len(samplers_by_batch)) ] # Build the output data structure to match the shape of input subexperiments @@ -164,7 +164,7 @@ def execute_experiments( count = 0 for i in range(len(subexperiments)): for j in range(len(subexperiments[0])): - if len(samplers_by_partition) == 1: + if len(samplers_by_batch) == 1: quasi_dists[i].append(quasi_dists_by_batch[0][count]) count += 1 else: From 0b15abb64ba28e063a52c350f7e49fcfbcdfa3ca Mon Sep 17 00:00:00 2001 From: Jim Garrison Date: Fri, 28 Jul 2023 10:34:09 -0400 Subject: [PATCH 21/22] Add SECURITY.md (#337) --- SECURITY.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..296fcd2c0 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,21 @@ +# Security Policy + +## Supported Versions + +The Circuit Knitting Toolbox supports one minor version release at a time, both for bug and +security fixes. For example, if the most recent release is 0.2.1, then the 0.2.x +release series is currently supported. + +## Reporting a Vulnerability + +To report vulnerabilities, you can privately report a potential security issue +via the GitHub security vulnerabilities feature. This can be done here: + +https://github.com/Qiskit-Extensions/circuit-knitting-toolbox/security/advisories + +Please do **not** open a public issue about a potential security vulnerability. + +You can find more details on the security vulnerability feature in the GitHub +documentation here: + +https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability From a5fb5b40a0ddf305f0b60f4aee7ab81ed3653c1b Mon Sep 17 00:00:00 2001 From: Jim Garrison Date: Fri, 28 Jul 2023 21:21:58 -0400 Subject: [PATCH 22/22] Implement wire cutting as a two-qubit instruction (#174) * Implement wire cutting as a two-qubit instruction * Update type annotation * s/gate/instruction/ * Add overhead test for `Move` instruction * Add wire cutting tutorial * Add `Move` to Sphinx build * Doc updates suggested by Caleb * Add release note and link to new tutorial * Clarify wording following https://github.com/Qiskit-Extensions/circuit-knitting-toolbox/pull/174#discussion_r1277527887 * Improvements to `Move` docstring * Use svg as the plot format This avoids pixelation on high-dpi displays * Remove unnecessary uses of `CircuitInstruction` https://github.com/Qiskit-Extensions/circuit-knitting-toolbox/pull/174/files#r1278067109 * The notebook tests should ignore any files that crop up in `docs/_build` `matplotlib.sphinxext.plot_directive` likes to leave python files there --- circuit_knitting/cutting/__init__.py | 1 + .../cutting/instructions/__init__.py | 18 + circuit_knitting/cutting/instructions/move.py | 82 +++ .../cutting/qpd/instructions/__init__.py | 2 +- circuit_knitting/cutting/qpd/qpd.py | 49 +- circuit_knitting/cutting/qpd/qpd_basis.py | 6 +- docs/circuit_cutting/cutqc/index.rst | 1 - docs/circuit_cutting/explanation/index.rst | 1 - ...03_wire_cutting_via_move_instruction.ipynb | 487 ++++++++++++++++++ docs/circuit_cutting/tutorials/README.rst | 2 + docs/conf.py | 5 + ...o-qubit-wire-cutting-27aff379403ea226.yaml | 6 + test/cutting/qpd/test_qpd.py | 6 +- test/cutting/qpd/test_qpd_basis.py | 4 +- test/cutting/test_cutting_roundtrip.py | 19 +- tox.ini | 2 +- 16 files changed, 669 insertions(+), 22 deletions(-) create mode 100644 circuit_knitting/cutting/instructions/__init__.py create mode 100644 circuit_knitting/cutting/instructions/move.py create mode 100644 docs/circuit_cutting/tutorials/03_wire_cutting_via_move_instruction.ipynb create mode 100644 releasenotes/notes/two-qubit-wire-cutting-27aff379403ea226.yaml diff --git a/circuit_knitting/cutting/__init__.py b/circuit_knitting/cutting/__init__.py index 8aeeda34d..f550008bf 100644 --- a/circuit_knitting/cutting/__init__.py +++ b/circuit_knitting/cutting/__init__.py @@ -35,6 +35,7 @@ PartitionedCuttingProblem CuttingExperimentResults + instructions.Move Quasi-Probability Decomposition (QPD) ===================================== diff --git a/circuit_knitting/cutting/instructions/__init__.py b/circuit_knitting/cutting/instructions/__init__.py new file mode 100644 index 000000000..9bb2b084b --- /dev/null +++ b/circuit_knitting/cutting/instructions/__init__.py @@ -0,0 +1,18 @@ +# This code is a Qiskit project. + +# (C) Copyright IBM 2023. + +# 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. + +r"""Quantum circuit :class:`~qiskit.Instruction`\ s useful for circuit cutting.""" + +from .move import Move + +__all__ = [ + "Move", +] diff --git a/circuit_knitting/cutting/instructions/move.py b/circuit_knitting/cutting/instructions/move.py new file mode 100644 index 000000000..522c06242 --- /dev/null +++ b/circuit_knitting/cutting/instructions/move.py @@ -0,0 +1,82 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023. +# +# 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. + +"""Two-qubit instruction representing a swap + single-qubit reset.""" +from __future__ import annotations + +from qiskit.circuit import QuantumCircuit, Instruction + + +class Move(Instruction): + """A two-qubit instruction representing a reset of the second qubit followed by a swap. + + **Circuit Symbol:** + + .. parsed-literal:: + + ┌───────┐ + q_0: ┤0 ├ q_0: ──────X─ + │ Move │ = │ + q_1: ┤1 ├ q_1: ─|0>──X─ + └───────┘ + + The desired effect of this instruction, typically, is to move the state of + the first qubit to the second qubit. For this to work as expected, the + second incoming qubit must share no entanglement with the remainder of the + system. If this qubit *is* entangled, then performing the reset operation + will in turn implement a quantum channel on the other qubit(s) with which + it is entangled, resulting in the partial collapse of those qubits. + + The simplest way to ensure that the second (i.e., destination) qubit shares + no entanglement with the remainder of the system is to use a fresh qubit + which has not been used since initialization. + + Another valid way is to use, as a desination qubit, a qubit whose immediate + prior use was as the source (i.e., first) qubit of a preceding + :class:`Move` operation. + + The following circuit contains two :class:`Move` operations, corresponding + to each of the aforementioned cases: + + .. plot:: + :include-source: + + import numpy as np + from qiskit import QuantumCircuit + from circuit_knitting.cutting.instructions import Move + + qc = QuantumCircuit(4) + qc.ryy(np.pi / 4, 0, 1) + qc.rx(np.pi / 4, 3) + qc.append(Move(), [1, 2]) + qc.rz(np.pi / 4, 0) + qc.ryy(np.pi / 4, 2, 3) + qc.append(Move(), [2, 1]) + qc.ryy(np.pi / 4, 0, 1) + qc.rx(np.pi / 4, 3) + qc.draw("mpl") + + A full demonstration of the :class:`Move` instruction is available in `the + introductory tutorial on wire cutting + <../circuit_cutting/tutorials/03_wire_cutting_via_move_instruction.ipynb>`__. + """ + + def __init__(self, label: str | None = None): + """Create a :class:`Move` instruction.""" + super().__init__("move", 2, 0, [], label=label) + + def _define(self): + """Set definition to equivalent circuit.""" + qc = QuantumCircuit(2, name=self.name) + qc.reset(1) + qc.swap(0, 1) + self.definition = qc diff --git a/circuit_knitting/cutting/qpd/instructions/__init__.py b/circuit_knitting/cutting/qpd/instructions/__init__.py index e93ca2ae0..e33f5fe62 100644 --- a/circuit_knitting/cutting/qpd/instructions/__init__.py +++ b/circuit_knitting/cutting/qpd/instructions/__init__.py @@ -9,7 +9,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -r"""Quantum circuit :class:`~qiskit.Instruction`\ s for repesenting quasiprobability decompositions.""" +r"""Quantum circuit :class:`~qiskit.Instruction`\ s for representing quasiprobability decompositions.""" from .qpd_gate import BaseQPDGate, SingleQubitQPDGate, TwoQubitQPDGate from .qpd_measure import QPDMeasure diff --git a/circuit_knitting/cutting/qpd/qpd.py b/circuit_knitting/cutting/qpd/qpd.py index 172e09fa7..a777d1d3e 100644 --- a/circuit_knitting/cutting/qpd/qpd.py +++ b/circuit_knitting/cutting/qpd/qpd.py @@ -25,9 +25,11 @@ from qiskit.circuit import ( QuantumCircuit, Gate, + Instruction, ClassicalRegister, CircuitInstruction, Measure, + Reset, ) from qiskit.circuit.library.standard_gates import ( XGate, @@ -68,6 +70,7 @@ from .qpd_basis import QPDBasis from .instructions import BaseQPDGate, TwoQubitQPDGate, QPDMeasure +from ..instructions import Move from ...utils.iteration import unique_by_id, strict_zip @@ -543,7 +546,7 @@ def decompose_qpd_instructions( return new_qc -_qpdbasis_from_gate_funcs: dict[str, Callable[[Gate], QPDBasis]] = {} +_qpdbasis_from_gate_funcs: dict[str, Callable[[Instruction], QPDBasis]] = {} def _register_qpdbasis_from_gate(*args): @@ -555,7 +558,7 @@ def g(f): return g -def qpdbasis_from_gate(gate: Gate) -> QPDBasis: +def qpdbasis_from_gate(gate: Instruction) -> QPDBasis: """ Generate a :class:`.QPDBasis` object, given a supported operation. @@ -564,12 +567,15 @@ def qpdbasis_from_gate(gate: Gate) -> QPDBasis: parameters, but there are some special cases (see, e.g., `qiskit issue #10396 `__). + The :class:`.Move` operation, which can be used to specify a wire cut, + is also supported. + Returns: The newly-instantiated :class:`QPDBasis` object Raises: - ValueError: Gate not supported. - ValueError: Cannot decompose gate with unbound parameters. + ValueError: Instruction not supported. + ValueError: Cannot decompose instruction with unbound parameters. ValueError: ``to_matrix`` conversion of two-qubit gate failed. """ try: @@ -598,10 +604,10 @@ def qpdbasis_from_gate(gate: Gate) -> QPDBasis: operations.append(UnitaryGate(d.K1l)) return retval - raise ValueError(f"Gate not supported: {gate.name}") + raise ValueError(f"Instruction not supported: {gate.name}") -def _explicitly_supported_gates() -> set[str]: +def _explicitly_supported_instructions() -> set[str]: """ Return a set of instruction names with explicit support for automatic decomposition. @@ -977,12 +983,41 @@ def _theta_from_gate(gate: Gate) -> float: theta = float(gate.params[0]) except TypeError as err: raise ValueError( - f"Cannot decompose ({gate.name}) gate with unbound parameters." + f"Cannot decompose ({gate.name}) instruction with unbound parameters." ) from err return theta +@_register_qpdbasis_from_gate("move") +def _(gate: Move): + i_measurement = [Reset()] + x_measurement = [HGate(), QPDMeasure(), Reset()] + y_measurement = [SdgGate(), HGate(), QPDMeasure(), Reset()] + z_measurement = [QPDMeasure(), Reset()] + + prep_0 = [Reset()] + prep_1 = [Reset(), XGate()] + prep_plus = [Reset(), HGate()] + prep_minus = [Reset(), XGate(), HGate()] + prep_iplus = [Reset(), HGate(), SGate()] + prep_iminus = [Reset(), XGate(), HGate(), SGate()] + + # https://arxiv.org/abs/1904.00102v2 Eqs. (12)-(19) + maps1, maps2, coeffs = zip( + (i_measurement, prep_0, 0.5), + (i_measurement, prep_1, 0.5), + (x_measurement, prep_plus, 0.5), + (x_measurement, prep_minus, -0.5), + (y_measurement, prep_iplus, 0.5), + (y_measurement, prep_iminus, -0.5), + (z_measurement, prep_0, 0.5), + (z_measurement, prep_1, -0.5), + ) + maps = list(zip(maps1, maps2)) + return QPDBasis(maps, coeffs) + + def _validate_qpd_instructions( circuit: QuantumCircuit, instruction_ids: Sequence[Sequence[int]] ): diff --git a/circuit_knitting/cutting/qpd/qpd_basis.py b/circuit_knitting/cutting/qpd/qpd_basis.py index af99e7dd4..848d30d95 100644 --- a/circuit_knitting/cutting/qpd/qpd_basis.py +++ b/circuit_knitting/cutting/qpd/qpd_basis.py @@ -15,7 +15,7 @@ from collections.abc import Sequence import numpy as np -from qiskit.circuit import Gate, Instruction +from qiskit.circuit import Instruction class QPDBasis: @@ -114,7 +114,7 @@ def overhead(self) -> float: return self._kappa**2 @staticmethod - def from_gate(gate: Gate) -> "QPDBasis": + def from_gate(gate: Instruction) -> QPDBasis: """ Generate a :class:`.QPDBasis` object, given a supported operation. @@ -122,7 +122,7 @@ def from_gate(gate: Gate) -> "QPDBasis": calls :func:`~qpd.qpd.qpdbasis_to_gate` under the hood. Args: - gate: The gate from which to instantiate a decomposition + gate: The instruction from which to instantiate a decomposition Returns: The newly-instantiated :class:`QPDBasis` object diff --git a/docs/circuit_cutting/cutqc/index.rst b/docs/circuit_cutting/cutqc/index.rst index b689d92fb..0e286f7d4 100644 --- a/docs/circuit_cutting/cutqc/index.rst +++ b/docs/circuit_cutting/cutqc/index.rst @@ -14,7 +14,6 @@ the new code. These features currently specific to ``cutqc`` include: - Reconstruction of probability distributions (rather than expectation values) - Automatic cut finding -- Wire cutting (rather than gate cutting) .. _cutqc tutorials: diff --git a/docs/circuit_cutting/explanation/index.rst b/docs/circuit_cutting/explanation/index.rst index 7e0566f05..e8e187a13 100644 --- a/docs/circuit_cutting/explanation/index.rst +++ b/docs/circuit_cutting/explanation/index.rst @@ -32,7 +32,6 @@ Key terms Current limitations ------------------- -* QPD-based wire cutting will be available no sooner than CKT v0.3.0. The `cutqc <../cutqc/index.rst>`__ package may be used for wire cutting in the meantime. * ``PauliList`` is the only supported observable format until no sooner than CKT v0.3.0. References diff --git a/docs/circuit_cutting/tutorials/03_wire_cutting_via_move_instruction.ipynb b/docs/circuit_cutting/tutorials/03_wire_cutting_via_move_instruction.ipynb new file mode 100644 index 000000000..0d951f70f --- /dev/null +++ b/docs/circuit_cutting/tutorials/03_wire_cutting_via_move_instruction.ipynb @@ -0,0 +1,487 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "9916de95-2376-49f0-91ad-07f07939dfb5", + "metadata": {}, + "source": [ + "# Wire Cutting Phrased as a Two-Qubit `Move` Instruction\n", + "\n", + "In this tutorial, we will reconstruct expectation values of a seven-qubit circuit by splitting it into two four-qubit circuits using wire cutting.\n", + "\n", + "Like any circuit knitting technique, wire cutting can be described as three consecutive steps:\n", + "\n", + "- **cut** some wires in the circuit and possibly separate the circuit into subcircuits\n", + "- **execute** many sampled subexperiments on the backend(s)\n", + "- **reconstruct** the simulated expectation value of the full-sized circuit" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "96420d42-678d-4fcc-bfe3-f69d777b9cc3", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from qiskit import QuantumCircuit\n", + "from qiskit.quantum_info import PauliList\n", + "from qiskit_aer.primitives import Estimator, Sampler\n", + "\n", + "from circuit_knitting.cutting.instructions import Move\n", + "from circuit_knitting.cutting import (\n", + " partition_problem,\n", + " execute_experiments,\n", + " reconstruct_expectation_values,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "ae63d837-a7f5-40a5-8186-f98076bb4cd9", + "metadata": {}, + "source": [ + "### Create a circuit to cut\n", + "\n", + "First, we begin with a circuit inspired by Fig. 1(a) of [arXiv:2302.03366v1](https://arxiv.org/abs/2302.03366v1)." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "3bcae0ed-4308-4686-b85c-8595c6e916bc", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "qc_0 = QuantumCircuit(7)\n", + "for i in range(7):\n", + " qc_0.rx(np.pi / 4, i)\n", + "qc_0.cx(0, 3)\n", + "qc_0.cx(1, 3)\n", + "qc_0.cx(2, 3)\n", + "qc_0.cx(3, 4)\n", + "qc_0.cx(3, 5)\n", + "qc_0.cx(3, 6)\n", + "qc_0.cx(0, 3)\n", + "qc_0.cx(1, 3)\n", + "qc_0.cx(2, 3)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "1dcaff2d-2d1b-4cc0-87d1-0f4f5de823ff", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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zc3lkUceqbS8TZs2LFu2zKJUIiJ11ysRVu2Awz+Z095dXcERUIeJciULmD/F8PBwXC4Xjz32mNVRPHb0xH5mLEiveP7JN6+SPl1X3RSp74IdMLKH0XtRk/SPap+4mdIKumoYRPxIwBQWgWhD7lJSE28BoMxZSt6BTdYGEhG/cU0U/Huv2ouLmrRrBqN6goeX5RHxiYAZCvF32XmZPPf+UOJapHCwcBftWnYmomEkjw55DYAl697hluvv5/2vnrU4qYj4i85tICwEZq+GEyV1+90b28E91+tKm+J/1GNhkk5t+9C+VTdeGZdJclwaj989gzNlp2gQGo7TdZbsvEy6xN9sdUwR8TPXtoD/ezv0TvSsSGgVCQ/1gxHdVVSIf9KfpUkKCnfSIjIOgCMn9lFUfIS4lsZpr8s2/J2bu4y0Mp6I+LGGoXDvDXB7CmzaC3uOQv5xKCkzJmVGhUNsJCTFQJsoq9OK1EyFhUn2HMyhTXQSLrcLm83OD9uXkppgzK/Yd2QbeQc2sWj1G+w5lMNnK19jSO/AmYQqIr7RIAR6xBsPkUClwsIkuw/l0KFND846SykqPswP25dxb5+JADx4+x8r1kuf3ltFhYiIXLFs5Z7eNKOeutR7hWRlzyUtZfhlbbtJLFz/28tqQkRExKfUY1GLiGaX9nt3xV5eUXE52xYREbGKeixERETENDrdVEREREyjwkJERERMo8JCRERETKPCQkREREyjwkJERERMo8JCRERETKPCQkREREyjwkJERERMo8JCRERETKPCQkREREyjwkJERERMo8JCRERETKPCQkREREyjwkJERERMo8JCRERETKPCQkREREyjwkJERERMo8JCRERETPP/AWejgXPrNDorAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "qc_0.draw(\"mpl\")" + ] + }, + { + "cell_type": "markdown", + "id": "bd1617a1-e793-43a9-a24a-c81c18fd2a1e", + "metadata": {}, + "source": [ + "### Specify some observables\n", + "\n", + "Next, we specify a list of observables whose expectation values we would like to determine." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "b791aa42-c485-453b-a110-3c790194adaf", + "metadata": {}, + "outputs": [], + "source": [ + "observables_0 = PauliList([\"ZIIIIII\", \"IIIZIII\", \"IIIIIIZ\"])" + ] + }, + { + "cell_type": "markdown", + "id": "9f56b094-0c6f-456f-9641-1424395fc6bd", + "metadata": {}, + "source": [ + "### Create a new circuit where `Move` instructions have been placed at the desired cut locations\n", + "\n", + "Given the above circuit, we would like to place two wire cuts on the middle qubit line, so that the circuit can separate into two circuits of four qubits each. One way to do this (currently, the only way) is to place two-qubit `Move` instructions that move the state from one qubit wire to another. A `Move` instruction is conceptually equivalent to a reset operation on the second qubit, followed by a SWAP gate. The effect of this instruction is to transfer the state of the first (source) qubit to the second (detination) qubit, while discarding the incoming state of the second qubit. For this to work as intended, it is important that the second (destination) qubit share no entanglement with the remainder of the system; otherwise, the reset operation will cause the state of the remainder of the system to be partially collapsed.\n", + "\n", + "Here, we build a new circuit with one additional qubit and the `Move` operations in place. In this example, we are able to reuse a qubit: the source qubit of the first `Move` becomes the destination qubit of the second `Move` operation." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "22f19d29-a182-4758-b5d0-6b66f9a946be", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "qc_1 = QuantumCircuit(8)\n", + "for i in [*range(4), *range(5, 8)]:\n", + " qc_1.rx(np.pi / 4, i)\n", + "qc_1.cx(0, 3)\n", + "qc_1.cx(1, 3)\n", + "qc_1.cx(2, 3)\n", + "qc_1.append(Move(), [3, 4])\n", + "qc_1.cx(4, 5)\n", + "qc_1.cx(4, 6)\n", + "qc_1.cx(4, 7)\n", + "qc_1.append(Move(), [4, 3])\n", + "qc_1.cx(0, 3)\n", + "qc_1.cx(1, 3)\n", + "qc_1.cx(2, 3)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "a52366f0-1160-44be-ac6b-a56e9b5bba7f", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAswAAAGMCAYAAADOYfTrAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8qNh9FAAAACXBIWXMAAAsTAAALEwEAmpwYAABNSElEQVR4nO3deXhU9cH+//fMJGQhYYmRLSwSsiiBBAgim4TNsriAC1Tg8StIRREXRPtrK2qt1NgitvgookUrWhUeQSqIgAUhUQyLgESgSiAsIRB2AgRClpn8/jgSCIRJQmbmnJj7dV1zkTlzcubO6My558znfMZWUlJSgoiIiIiIlMtudgAREREREStTYRYRERERcUOFWURERETEDRVmERERERE3VJhFRERERNxQYRYRERERcUOFWURERETEDRVmERERERE3VJhFRERERNxQYRYRERERcUOFWURERETEDRVmERERERE3VJhFRERERNxQYRYRERERcUOFWURERETEDRVmERERERE3VJhFRERERNxQYRYRERERcUOFWURERETEDRVmERERERE3VJhFRERERNxQYRYRERERcUOFWURERETEDRVmERERERE3VJhFRERERNxQYRYRERERcUOFWURERETEDT+zA1jd9pVw+rA59x3aCGL7mnPfIiIiImJQYa7A6cOQm212ChERERExi4ZkiIiIiIi4ocIsIiIiIuKGCrOIiIiIiBsaw+wBT83szY971+Bw+GO3O2jSsDUj+00mKWGY2dFEREREpJpUmD1kVP/nGNX/WZzOYhamvcHLH48kKqIjEeFRZkcTERERkWrQkAwPczj8GHTTgzhdxWQe2Gx2HBERERGpJhVmDysqLmRx2kwAmofHmJxGRERERKpLQzI85OOvXmJe6jTyC07jcPgzadg7RDaLB2D/0Z289OGvee3RNfj71eGTlFc4W3Ca0QNeNDm1iIiIiFTE0keYXS4X06ZNIzo6msDAQBISEkhNTSU2NpZx48aZHa+Mkf0m89mUXOa/cJQu1w8mfeeq0tsiwqPo2f5u5q58mZzju0nZPJeR/SabmPZyxU7YsBveWgmvLIG3V8KmPcZyERGR2ujwKfj3RvjbMuPy743GMql9LH2EeezYsSxYsIDnnnuOxMRE0tLSGDFiBEeOHGHSpElmxytXaHBDJg17h/v/0oa0rQvp3m4I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MvEj49/KPj5mcLrgjwvKngF8KRvQPAzyTx/zWS4REfllqgn7xtUZMP879+vY7Q7e+PME4mZPcL+iXMaSQzLsdjvz5s0jLi6O8ePHM2bMGMLDw5kwYQIOh6P0hD+pnRx26BXrfp0SIEljtEREpJbo3BqC/I0DRuWx2eCaELihmU9j/WJY8ggzQExMDKtWrSqz7L777qNt27YEBQWZlEqsol8cZJ+AH/YZLw7n3/Of/znpeki8zrR4IiIiPhXoDw/2hrdWQWHx5bfXrQO/SQK7JQ+VWl+Netg2bNhw2XCM5557jubNm7NmzRoeeughmjdvTmZmpkkJxVccdhjdE/6nO7S85sLy6CbGC8LQTsa7aRERkdoishH8f4PLfgobGgi3xMFvb4WmDUyLVuPVmMKcl5dHRkbGZV9YMmXKFLKzsykoKODYsWNkZ2fTpk0bk1KWdfTkft5cOLH0+qdf/52JM/SlJZ5itxsfQT058MKyR/pBu+YqyyIiUjuFh8JdnS9cn3K38S1/9fXhfLVYdkjGpUJCQnA63U+XYjUbM5aTGHMLAIXFBWQe2GxuIBERERGpshpTmK0uPTOFF96/k8imCRw8vps2zToQGhzGo0NfB2DZ+ne5pfP9vP/l8yYnFREREZGqqDFDMqyufetexLbowqvjU4iPTOLxu97kXOEZggJCKHYWkZ6ZQseovmbHFBEREZEqUmH2kJzju2gaZnzj4JGT+8jNO0JkswQAVmz8F307jjQznoiIiIhcJRVmD9l7cButmsThdDmx2exs2rGcxGhj/PK+I9v5fM1M/jBrIHsPbeOz1a+bnFZEREREKktjmD1kz6FttG3VjaLiAnLzDrNpxwru6fUUAA/e+tfS9SbO6MnQno+ZFVNEREREqkiF2UNG9num9OdZT20hNf0T7OXMDj59wmpfxhIRERGRatKQDC9JShhudgQRERER8QAVZhERERERNzQkowKhjWrnfYuIiIiIQYW5ArGaOllERESkVtOQDBERERERN1SYRURERETcUGEWEREREXFDhVlERERExA0VZhERERERN1SYRURERETcUGEWEREREXFDhVlERERExA0VZhERERERN1SYRURERETcUGEWEREREXFDhVlERERExA0VZhERERERN/zMDmB121fC6cPm3HdoI4jta859i4iIiIhBhbkCpw9DbrbZKURE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QYAxdGPgwIF88cUXABw7doz169eTkJBgZnQRkV+cyEae32YdBzRXYRaRGsCyhbk8GzZsuOwLS9566y3efvtt2rdvT1JSEr///e/p2rWrSQlFRH6Zukd7fpuJrSHAkgMDRUTKqjEvVXl5eWRkZPDII4+UWd6qVStWrFhhUioRkdqh1TXQ+lrYfcQz27PboFesZ7YlIuJtNeYIc0hICE6nk8cee8zsKJV29OR+3lw4sfT6p1//nYkz9C1/IlLz2Gxw703g56G9xi3toGkDz2xLRMTbaswR5ppoY8ZyEmNuAaCwuIDMA5vNDSQiUg2N68M9XWDu2iuvU5nZMaIbwy1xnsslIuJtKswekp6Zwgvv30lk0wQOHt9Nm2YdCA0O49GhrwOwbP273NL5ft7/0vwJ+kVErlbXNuB0wvzv4GomYItpAmN7gZ/D49FERLymxgzJsLr2rXsR26ILr45PIT4yicfvepNzhWcICgih2FlEemYKHaP6mh1TRKTaesTA47+CRvUq/zt+dri9AzzUBwL8vRZNRMQrdITZQ3KO76JpWCQAR07uIzfvCJHNjOntVmz8F307jjQznoiIR7W+Fn47GL7fC6szIOtY+evVDTCOSnePhmu8NJeziIi3qTB7yN6D22jVJA6ny4nNZmfTjuUkRhvjl/cd2U7mgc0sXvMWew9t47PVrzO0Z805eVFEpDz+DugSaVzOFMC+43A8D0pKjC87aR4G4aHGjBgiIjWZCrOH7Dm0jbatulFUXEBu3mE27VjBPb2eAuDBW/9aut7EGT1VlkXkF6duAFzf1OwUIiLeocLsISP7PVP686yntpCa/gl2++VDxKdPWO3LWCIiIiJSTTrpz0uSEoabHUFEREREPEBHmCsQ2qh23reIiIiIGFSYKxCrmeBEREREajUNyRARERERcUOFWURERETEDRVmERERERE3VJhFRERERNxQYRYRERERcUOFWURERETEDRVmERERERE3VJhFRERERNxQYRYRERERcUOFWURERETEDRVmERERERE3VJhFRERERNxQYRYRERERccPP7ABWt30lnD5szn2HNoLYvubct4iIiIgYVJgrcPow5GabnUJEREREzKIhGSIiIiIibqgwi4iIiIi4oSEZIiJS65wrgi37IOsYHMi9sPzDNGgRBu2awzUhpsUTEYtRYfaAp2b25se9a3A4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\n", + "text/plain": [ + "
" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "qc_1.draw(\"mpl\")" + ] + }, + { + "cell_type": "markdown", + "id": "088431d9-de2f-4b5d-90d2-7e7a5947dcb5", + "metadata": {}, + "source": [ + "### Create observables to go with the new circuit\n", + "\n", + "These observables correspond with `observables_0`, but we must account correctly for the extra qubit wire that has been added (i.e., we insert an \"I\" at index 4). Note that in Qiskit, the string representation qubit-0 corresponds to the right-most Pauli character." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "d33d5580-879f-466f-ac87-dcc6a19fbab6", + "metadata": {}, + "outputs": [], + "source": [ + "observables_1 = PauliList([\"ZIIIIIII\", \"IIIIZIII\", \"IIIIIIIZ\"])" + ] + }, + { + "cell_type": "markdown", + "id": "a00aeede-3d0f-4589-b1dc-3cd7df9c4085", + "metadata": {}, + "source": [ + "### Separate the circuit and observables\n", + "\n", + "As in the previous tutorials, qubits sharing a common partition label will be grouped together, and non-local gates spanning more than one partition will be cut." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "fc3738c7-2bb2-4d67-ae2b-7a3090b31e6a", + "metadata": {}, + "outputs": [], + "source": [ + "subcircuits, bases, subobservables = partition_problem(\n", + " circuit=qc_1, partition_labels=\"AAAABBBB\", observables=observables_1\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "fd0b0d6c-de39-459c-8b97-6831dab6b3be", + "metadata": {}, + "source": [ + "`execute_experiments` returns:\n", + "\n", + "- A 3D list of length-2 tuples containing a quasiprobability distribution and QPD bit information for each unique subexperiment\n", + "- The coefficients for each subexperiment" + ] + }, + { + "cell_type": "markdown", + "id": "4c8a76eb-31db-486c-b7c2-a5fe3cb732c2", + "metadata": {}, + "source": [ + "### Visualize the decomposed problem" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "9c53a862-f762-471a-bda7-b27e88292ac9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'A': PauliList(['IIII', 'ZIII', 'IIIZ']),\n", + " 'B': PauliList(['ZIII', 'IIII', 'IIII'])}" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "subobservables" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "69df912e-6709-45bb-8eb3-c66a70edefdc", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "subcircuits[\"A\"].draw(\"mpl\")" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "d851adcb-e524-48c8-8adc-0d1606613c8d", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "subcircuits[\"B\"].draw(\"mpl\")" + ] + }, + { + "cell_type": "markdown", + "id": "9c177fb7-a729-4e0d-bfac-256df3e14c54", + "metadata": {}, + "source": [ + "### Calculate the sampling overhead for the chosen cuts\n", + "\n", + "The sampling overhead is the factor by which the number of samples must increase for the quasiprobability decomposition to result in the same amount of error, $\\epsilon$, as one would get by sampling the original circuit. Cutting wires with local operations (LO) only incurs a sampling overhead of $4^{2k}$, where $k$ is the number of cuts [[Brenner, Piveteau, Sutter]](https://arxiv.org/abs/2302.03366).\n", + "\n", + "Here we cut two wires, resulting in a sampling overhead of $4^4$." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "7af74c54-3e68-4d58-a2e6-02bc212d911d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Sampling overhead: 256.0\n" + ] + } + ], + "source": [ + "print(f\"Sampling overhead: {np.prod([basis.overhead for basis in bases])}\")" + ] + }, + { + "cell_type": "markdown", + "id": "a1eda2d0-8d83-473b-8ea8-fe9880108140", + "metadata": {}, + "source": [ + "### Generate and run the cutting experiments\n", + "\n", + "`execute_experiments` accepts `circuits`/`subobservables` args as dictionaries mapping qubit partition labels to the respective `subcircuit`/`subobservables`.\n", + "\n", + "To simulate the expectation value of the full-sized circuit, many subexperiments are generated from the decomposed gates' joint quasiprobability distribution and then executed on one or more backends.\n", + "\n", + "The number of weights taken from the distribution is controlled by `num_samples`. Each weight whose absolute value is above a threshold of 1 / `num_samples` will be evaluated exactly. The remaining low-probability elements -- those in the tail of the distribution -- will then be sampled from, resulting in at most `num_samples` unique weights.\n", + "\n", + "Much of the circuit cutting literature describes a process where we sample from the distribution, take a single shot, then sample from the distribution again and repeat; however, this is not feasible in practice. The total number of shots needed grows exponentially with the number of cuts, and taking single shot experiments via Qiskit Runtime quickly becomes untenable. Instead, we take an equivalent number of shots for each considered subexperiment and send them to the backend(s) in batches. During reconstruction, each subexperiment contributes to the final result with proportion equal to its weight. We just need to ensure the number of shots we take is appropriate for the heaviest weights, and thus, appropriate for all weights." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "d28a8b82-8405-47b2-8142-62d56266409a", + "metadata": {}, + "outputs": [], + "source": [ + "# Keep in mind, Terra Sampler does not support mid-circuit measurements at all,\n", + "# and Aer Sampler does not support mid-circuit measurements when shots==None.\n", + "samplers = {\n", + " \"A\": Sampler(run_options={\"shots\": 2**12}),\n", + " \"B\": Sampler(run_options={\"shots\": 2**12}),\n", + "}\n", + "\n", + "quasi_dists, coefficients = execute_experiments(\n", + " circuits=subcircuits,\n", + " subobservables=subobservables,\n", + " num_samples=1500,\n", + " samplers=samplers,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "f14e87b7-17a1-4da0-9f7d-21c0935ef75e", + "metadata": {}, + "source": [ + "`execute_experiments` returns:\n", + "\n", + "- A 3D list of length-2 tuples containing a quasiprobability distribution and QPD bit information for each unique subexperiment\n", + "- The coefficients for each subexperiment" + ] + }, + { + "cell_type": "markdown", + "id": "5c5fdec5-89ed-480f-833f-7377c33ad365", + "metadata": {}, + "source": [ + "### Reconstruct the simulated expectation values\n", + "\n", + "`reconstruct_expectation_values` expects `quasi_dists` and `coefficients` in the same format as returned from `execute_experiments`. `quasi_dists` is a 3D list of shape (`num_unique_samples`, `num_partitions`, `num_commuting_observ_groups`), and `coefficients` is a list with length equal to the number of unique samples. `subobservables` is the dictionary mapping qubit partition label to the associated subobservable(s), as output from `decompose_problem` above." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "d0301992-04c4-4882-aef8-890ad741ff1e", + "metadata": {}, + "outputs": [], + "source": [ + "reconstructed_expvals = reconstruct_expectation_values(\n", + " quasi_dists,\n", + " coefficients,\n", + " subobservables,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "cdc793f2-3e2b-417b-b863-7b9d57b86a8f", + "metadata": {}, + "source": [ + "### Compare the reconstructed expectation values with the exact expectation values from the original circuit" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "6e3d63e4-a510-4712-bc43-48df6e2f7ded", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Reconstructed expectation values: [0.20690966, 0.73720598, 0.70476413]\n", + "Exact expectation values: [0.1767767, 0.70710678, 0.70710678]\n", + "Errors in estimation: [0.03013296, 0.0300992, -0.00234265]\n", + "Relative errors in estimation: [0.17045777, 0.0425667, -0.00331301]\n" + ] + } + ], + "source": [ + "estimator = Estimator(run_options={\"shots\": None}, approximation=True)\n", + "exact_expvals = (\n", + " estimator.run([qc_0] * len(observables_0), list(observables_0)).result().values\n", + ")\n", + "print(\n", + " f\"Reconstructed expectation values: {[np.round(reconstructed_expvals[i], 8) for i in range(len(exact_expvals))]}\"\n", + ")\n", + "print(\n", + " f\"Exact expectation values: {[np.round(exact_expvals[i], 8) for i in range(len(exact_expvals))]}\"\n", + ")\n", + "print(\n", + " f\"Errors in estimation: {[np.round(reconstructed_expvals[i]-exact_expvals[i], 8) for i in range(len(exact_expvals))]}\"\n", + ")\n", + "print(\n", + " f\"Relative errors in estimation: {[np.round((reconstructed_expvals[i]-exact_expvals[i]) / exact_expvals[i], 8) for i in range(len(exact_expvals))]}\"\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/circuit_cutting/tutorials/README.rst b/docs/circuit_cutting/tutorials/README.rst index c8ed8fba3..20e78091e 100644 --- a/docs/circuit_cutting/tutorials/README.rst +++ b/docs/circuit_cutting/tutorials/README.rst @@ -6,3 +6,5 @@ Circuit Cutting Tutorials subexperiments for each qubit partition in parallel. - `Tutorial 2 <02_gate_cutting_to_reduce_circuit_depth.ipynb>`__: Cut gates requiring many SWAPs to decrease circuit depth. +- `Tutorial 3 <03_wire_cutting_via_move_instruction.ipynb>`__: + Specify wire cuts as a two-qubit :class:`.Move` operation. diff --git a/docs/conf.py b/docs/conf.py index 269bf31f8..c1abc2707 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -41,6 +41,7 @@ "sphinx.ext.mathjax", "sphinx.ext.viewcode", "sphinx.ext.extlinks", + "matplotlib.sphinxext.plot_directive", # "sphinx.ext.autosectionlabel", "jupyter_sphinx", "sphinx_autodoc_typehints", @@ -96,6 +97,10 @@ "**/README.rst", ] +# matplotlib.sphinxext.plot_directive options +plot_html_show_formats = False +plot_formats = ["svg"] + # Redirects for pages that have moved redirects = { "circuit_cutting/tutorials/gate_cutting_to_reduce_circuit_width.html": "01_gate_cutting_to_reduce_circuit_width.html", diff --git a/releasenotes/notes/two-qubit-wire-cutting-27aff379403ea226.yaml b/releasenotes/notes/two-qubit-wire-cutting-27aff379403ea226.yaml new file mode 100644 index 000000000..ed64ac656 --- /dev/null +++ b/releasenotes/notes/two-qubit-wire-cutting-27aff379403ea226.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + The :mod:`circuit_knitting.cutting` module now supports wire + cutting. There is a :ref:`new tutorial ` that explains how to use it. diff --git a/test/cutting/qpd/test_qpd.py b/test/cutting/qpd/test_qpd.py index 6c1bb66de..6cd75c561 100644 --- a/test/cutting/qpd/test_qpd.py +++ b/test/cutting/qpd/test_qpd.py @@ -47,7 +47,7 @@ _generate_exact_weights_and_conditional_probabilities, _nonlocal_qpd_basis_from_u, _u_from_thetavec, - _explicitly_supported_gates, + _explicitly_supported_instructions, ) @@ -267,6 +267,7 @@ def test_decompose_qpd_instructions(self): (SwapGate(), 7), (iSwapGate(), 7), (DCXGate(), 7), + (Move(), 4), ) @unpack def test_optimal_kappa_for_known_gates(self, instruction, gamma): @@ -427,7 +428,7 @@ def from_theta(theta): assert weights[map_ids][1] == WeightType.SAMPLED def test_explicitly_supported_gates(self): - gates = _explicitly_supported_gates() + gates = _explicitly_supported_instructions() self.assertEqual( { "rxx", @@ -448,6 +449,7 @@ def test_explicitly_supported_gates(self): "swap", "iswap", "dcx", + "move", }, gates, ) diff --git a/test/cutting/qpd/test_qpd_basis.py b/test/cutting/qpd/test_qpd_basis.py index eb67ed453..a02186f4c 100644 --- a/test/cutting/qpd/test_qpd_basis.py +++ b/test/cutting/qpd/test_qpd_basis.py @@ -121,7 +121,7 @@ def test_eq(self): def test_unsupported_gate(self): with pytest.raises(ValueError) as e_info: QPDBasis.from_gate(C3XGate()) - assert e_info.value.args[0] == "Gate not supported: mcx" + assert e_info.value.args[0] == "Instruction not supported: mcx" def test_unbound_parameter(self): with self.subTest("Explicitly supported gate"): @@ -131,7 +131,7 @@ def test_unbound_parameter(self): QPDBasis.from_gate(RZZGate(Parameter("θ"))) assert ( e_info.value.args[0] - == "Cannot decompose (rzz) gate with unbound parameters." + == "Cannot decompose (rzz) instruction with unbound parameters." ) with self.subTest("Implicitly supported gate"): # For implicitly supported gates, we can detect that `to_matrix` diff --git a/test/cutting/test_cutting_roundtrip.py b/test/cutting/test_cutting_roundtrip.py index 0004e2a99..89f82be21 100644 --- a/test/cutting/test_cutting_roundtrip.py +++ b/test/cutting/test_cutting_roundtrip.py @@ -16,7 +16,6 @@ import numpy as np from qiskit import QuantumCircuit -from qiskit.circuit import CircuitInstruction from qiskit.circuit.library.standard_gates import ( RXXGate, RYYGate, @@ -50,7 +49,7 @@ execute_experiments, reconstruct_expectation_values, ) - +from circuit_knitting.cutting.instructions import Move logger = logging.getLogger(__name__) @@ -90,6 +89,8 @@ def append_random_unitary(circuit: QuantumCircuit, qubits): [RZXGate(np.pi / 5)], [XXPlusYYGate(7 * np.pi / 11)], [XXMinusYYGate(11 * np.pi / 17)], + [Move()], + [Move(), Move()], ] ) def example_circuit( @@ -106,10 +107,20 @@ def example_circuit( qc = QuantumCircuit(3) cut_indices = [] for instruction in request.param: - append_random_unitary(qc, [0, 1]) + if instruction.name == "move" and len(cut_indices) % 2 == 1: + # We should not entangle qubit 1 with the remainder of the system. + # In fact, we're also assuming that the previous operation here was + # a move. + append_random_unitary(qc, [0]) + append_random_unitary(qc, [1]) + else: + append_random_unitary(qc, [0, 1]) append_random_unitary(qc, [2]) cut_indices.append(len(qc.data)) - qc.append(CircuitInstruction(instruction, [np.random.choice([0, 1]), 2])) + qubits = [1, 2] + if len(cut_indices) % 2 == 0: + qubits.reverse() + qc.append(instruction, qubits) qc.barrier() append_random_unitary(qc, [0, 1]) qc.barrier() diff --git a/tox.ini b/tox.ini index 7e5c278f3..99820b220 100644 --- a/tox.ini +++ b/tox.ini @@ -37,7 +37,7 @@ extras = nbtest notebook-dependencies commands = - pytest --nbmake --nbmake-timeout=3000 {posargs} docs/ --ignore=docs/entanglement_forging/tutorials/tutorial_2_forging_with_quantum_serverless.ipynb + pytest --nbmake --nbmake-timeout=3000 {posargs} docs/ --ignore=docs/_build --ignore=docs/entanglement_forging/tutorials/tutorial_2_forging_with_quantum_serverless.ipynb [testenv:coverage] basepython = python3.10