Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dont batch unless separate samplers are used for each subcircuit #333

Merged
merged 23 commits into from
Aug 14, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 38 additions & 25 deletions circuit_knitting/cutting/cutting_evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,30 +128,47 @@ def execute_experiments(
num_samples,
)

# Create a list of samplers -- one for each qubit partition
num_partitions = len(subexperiments[0])
# Create a list of samplers to use -- one for each batch
if isinstance(samplers, BaseSampler):
samplers_by_partition = [samplers] * num_partitions
samplers_by_partition = [samplers]
garrison marked this conversation as resolved.
Show resolved Hide resolved
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())]
batches = [
[sample[i] for sample in subexperiments]
for i in range(len(subexperiments[0]))
]

# Run each partition's sub-experiments
quasi_dists_by_partition = [
# 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(
[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)
# 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(num_unique_samples)
[] for _ in range(len(subexperiments))
]
for i in range(num_unique_samples):
for partition in quasi_dists_by_partition:
quasi_dists[i].append(partition[i])
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:
quasi_dists[i].append(quasi_dists_by_batch[j][i])

return CuttingExperimentResults(quasi_dists, coefficients)

Expand Down Expand Up @@ -327,18 +344,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: list[list[QuasiDistribution]] = [[] for _ in subexperiments]
Copy link
Collaborator Author

@caleb-johnson caleb-johnson Jul 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't rely on numpy here anymore because there are now potentially different subcircuits coming in on the same batch, and those can have different numbers of subexperiments associated with them; therefore, we can't trivially reshape to the input size anymore.

We now have to "stream" the quasi-dists into the output data structure, using the input as a guide for how to re-build the structure

num_qpd_bits: list[list[int]] = [[] 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]]] = [
Expand Down
5 changes: 5 additions & 0 deletions releasenotes/notes/batch-by-sampler-c4ae836df9997b1d.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
upgrade:
- |
: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.