Skip to content

Commit

Permalink
Use Rust generator function for QuantumVolume class (#13283)
Browse files Browse the repository at this point in the history
* Use Rust generator function for QuantumVolume class

In #13238 we added a new function quantum_volume for generating a
quantum volume model circuit with the eventual goal of replacing the
existing QuantumVolume class. This new function is ~10x faster to
generate the circuit than the existing python class. This commit builds
off of that to internally call the new function for generating the
circuit from the class. While the plan is to deprecate and remove the
class for Qiskit 2.0 until that time we can give a performance boost to
users of it for the lifespan of the 1.x release series.

* Use seed_name in string generation

* Fix rng call
  • Loading branch information
mtreinish authored Oct 16, 2024
1 parent 344cb7a commit 50f2965
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 20 deletions.
50 changes: 30 additions & 20 deletions qiskit/circuit/library/quantum_volume.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,39 +83,49 @@ def __init__(
depth = depth or num_qubits # how many layers of SU(4)
width = num_qubits // 2 # how many SU(4)s fit in each layer
rng = seed if isinstance(seed, np.random.Generator) else np.random.default_rng(seed)
if seed is None:
seed_name = seed
if seed_name is None:
# Get the internal entropy used to seed the default RNG, if no seed was given. This
# stays in the output name, so effectively stores a way of regenerating the circuit.
# This is just best-effort only, for backwards compatibility, and isn't critical (if
# someone needs full reproducibility, they should be manually controlling the seeding).
seed = getattr(getattr(rng.bit_generator, "seed_seq", None), "entropy", None)
seed_name = getattr(getattr(rng.bit_generator, "seed_seq", None), "entropy", None)

super().__init__(
num_qubits, name="quantum_volume_" + str([num_qubits, depth, seed]).replace(" ", "")
num_qubits,
name="quantum_volume_" + str([num_qubits, depth, seed_name]).replace(" ", ""),
)
base = self if flatten else QuantumCircuit(num_qubits, name=self.name)

# For each layer, generate a permutation of qubits
# Then generate and apply a Haar-random SU(4) to each pair
unitaries = scipy.stats.unitary_group.rvs(4, depth * width, rng).reshape(depth, width, 4, 4)
qubits = tuple(base.qubits)
for row in unitaries:
perm = rng.permutation(num_qubits)
if classical_permutation:
for w, unitary in enumerate(row):
gate = UnitaryGate(unitary, check_input=False, num_qubits=2)
qubit = 2 * w
base._append(
CircuitInstruction(gate, (qubits[perm[qubit]], qubits[perm[qubit + 1]]))
)
if classical_permutation:
if seed is not None:
max_value = np.iinfo(np.int64).max
seed = rng.integers(max_value, dtype=np.int64)
qv_circ = quantum_volume(num_qubits, depth, seed)
qv_circ.name = self.name
if flatten:
self.compose(qv_circ, inplace=True)
else:
self._append(CircuitInstruction(qv_circ.to_instruction(), tuple(self.qubits)))
else:
if seed is None:
seed = seed_name

base = self if flatten else QuantumCircuit(num_qubits, name=self.name)

# For each layer, generate a permutation of qubits
# Then generate and apply a Haar-random SU(4) to each pair
unitaries = scipy.stats.unitary_group.rvs(4, depth * width, rng).reshape(
depth, width, 4, 4
)
qubits = tuple(base.qubits)
for row in unitaries:
perm = rng.permutation(num_qubits)
base._append(CircuitInstruction(PermutationGate(perm), qubits))
for w, unitary in enumerate(row):
gate = UnitaryGate(unitary, check_input=False, num_qubits=2)
qubit = 2 * w
base._append(CircuitInstruction(gate, qubits[qubit : qubit + 2]))
if not flatten:
self._append(CircuitInstruction(base.to_instruction(), tuple(self.qubits)))
if not flatten:
self._append(CircuitInstruction(base.to_instruction(), tuple(self.qubits)))


def quantum_volume(
Expand Down
17 changes: 17 additions & 0 deletions releasenotes/notes/add-qv-function-a8990e248d5e7e1a.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,20 @@ features_circuits:
a :class:`.QuantumCircuit` object instead of building a subclass object. The second is
that this new function is multithreaded and implemented in rust so it generates the output
circuit ~10x faster than the :class:`.QuantumVolume` class.
- |
Improved the runtime performance of constructing the
:class:`.QuantumVolume` class with the ``classical_permutation`` argument set
to ``True``. Internally it now calls the :func:`.quantum_volume`
function which is written in Rust which is ~10x faster to generate a
quantum volume circuit.
upgrade_circuits:
- |
The :class:`.QuantumVolume` class will generate circuits with
different unitary matrices and permutations for a given seed value from
the previous Qiskit release. This is due to using a new internal random
number generator for the circuit generation that will generate the circuit
more quickly. If you need an exact circuit with the same seed you can
use the previous release of Qiskit and generate the circuit with the
``flatten=True`` argument and export the circuit with :func:`.qpy.dump`
and then load it with this release.

0 comments on commit 50f2965

Please sign in to comment.