File size: 3,325 Bytes
2a87ede
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
"""

Random circuit generation for benchmarking and fuzz-testing -- the same

purpose Qiskit's `random_circuit` serves.

"""
import numpy as np

__all__ = ['random_circuit']

_1Q_STATIC = ('h', 'x', 'y', 'z', 's', 'sdg', 't', 'tdg', 'sx')
_1Q_PARAMETRIC = ('rx', 'ry', 'rz')
_2Q_STATIC = ('cx', 'cz', 'cy', 'swap')
_ALL_KNOWN = set(_1Q_STATIC) | set(_1Q_PARAMETRIC) | set(_2Q_STATIC)


def random_circuit(n_qubits, n_gates, seed=None, gate_set=None, two_qubit_prob=0.4):
    """

    Build a random circuit for benchmarking or fuzz-testing.



    Parameters

    ----------

    n_qubits : int

        Number of qubits, must be >= 1.

    n_gates : int

        Number of gate operations to generate, must be >= 0.

    seed : int | numpy.random.Generator, optional

        Seed (or an existing Generator) for reproducible circuits.

    gate_set : iterable of str, optional

        Restrict generation to this set of gate names, mixing 1- and

        2-qubit gates freely (default spans both: h, x, y, z, s, sdg, t,

        tdg, sx, rx, ry, rz, cx, cz, cy, swap). Unrecognized names raise

        immediately rather than failing later inside run_circuit.

    two_qubit_prob : float

        Probability in [0, 1] of picking a 2-qubit gate at each step.

        Ignored (forced to single-qubit gates) once n_qubits < 2, or once

        gate_set excludes every 2-qubit gate name.



    Returns

    -------

    list[tuple]

    """
    if n_qubits < 1:
        raise ValueError(f"random_circuit needs at least 1 qubit, got {n_qubits}")
    if n_gates < 0:
        raise ValueError(f"n_gates must be >= 0, got {n_gates}")
    if not (0.0 <= two_qubit_prob <= 1.0):
        raise ValueError(f"two_qubit_prob must be in [0, 1], got {two_qubit_prob}")

    rng = seed if isinstance(seed, np.random.Generator) else np.random.default_rng(seed)

    if gate_set is not None:
        gate_set = list(gate_set)
        unknown = set(gate_set) - _ALL_KNOWN
        if unknown:
            raise ValueError(f"unknown gate name(s) in gate_set: {sorted(unknown)}")
        one_q_static = [g for g in gate_set if g in _1Q_STATIC]
        one_q_param = [g for g in gate_set if g in _1Q_PARAMETRIC]
        two_q = [g for g in gate_set if g in _2Q_STATIC]
    else:
        one_q_static = list(_1Q_STATIC)
        one_q_param = list(_1Q_PARAMETRIC)
        two_q = list(_2Q_STATIC) if n_qubits >= 2 else []

    one_q_pool = one_q_static + one_q_param
    if not one_q_pool and not two_q:
        raise ValueError("gate_set leaves no usable gates for this n_qubits")

    ops = []
    for _ in range(n_gates):
        use_two_qubit = (
            bool(two_q) and n_qubits >= 2
            and (not one_q_pool or rng.random() < two_qubit_prob)
        )
        if use_two_qubit:
            name = two_q[rng.integers(len(two_q))]
            a, b = rng.choice(n_qubits, size=2, replace=False)
            ops.append((name, int(a), int(b)))
        else:
            name = one_q_pool[rng.integers(len(one_q_pool))]
            q = int(rng.integers(n_qubits))
            if name in _1Q_PARAMETRIC:
                theta = float(rng.uniform(0, 2 * np.pi))
                ops.append((name, q, theta))
            else:
                ops.append((name, q))
    return ops