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

Add random circuit with graph #12474

Open
wants to merge 39 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
fd31959
Added random_circuit_with_graph
MozammilQ May 29, 2024
e173505
added tests
MozammilQ May 29, 2024
f0b3e5a
added release note
MozammilQ May 29, 2024
81a5207
lint
MozammilQ May 29, 2024
7238cac
fix docs lint
MozammilQ May 29, 2024
7195850
fix lint
MozammilQ May 29, 2024
69f7824
refactor code;lint
MozammilQ May 29, 2024
940eed5
refactor code;lint
MozammilQ May 29, 2024
692b052
refactor code;lint
MozammilQ May 30, 2024
aca4e1a
refactor code; added suggestions
MozammilQ Jun 10, 2024
b359f6a
Merge branch 'main' into add_random_circuit_with_graph
MozammilQ Jun 10, 2024
aa5bf41
fix weird merge conflict
MozammilQ Jun 10, 2024
79efdf5
edit docstrings; fix typos
MozammilQ Jun 11, 2024
4c69570
added a test;fixed lint
MozammilQ Jun 11, 2024
8ae7db4
added suggestions
MozammilQ Jun 18, 2024
ecd9cec
refactor tests; added tests; lint
MozammilQ Jun 19, 2024
fecfd4c
added a few more tests
MozammilQ Jun 19, 2024
79fbc39
added rel_diff; altered docstrings; added tests
MozammilQ Jun 19, 2024
808ec92
Merge branch 'Qiskit:main' into add_random_circuit_with_graph
MozammilQ Jun 19, 2024
35b6552
restoring symengine version
MozammilQ Jun 19, 2024
5f4dc1a
added rel_diff to docstrings; releasenote; lint
MozammilQ Jun 19, 2024
99f0b12
removed rel_diff
MozammilQ Jul 4, 2024
b2788d3
added suggestion; refactor code
MozammilQ Jul 5, 2024
b73d44f
added a bit of explanation; removed delay
MozammilQ Jul 5, 2024
e621d24
altered comments
MozammilQ Jul 11, 2024
a955890
Merge branch 'Qiskit:main' into add_random_circuit_with_graph
MozammilQ Oct 28, 2024
2335b59
Merge branch 'main' into add_random_circuit_with_graph
MozammilQ Oct 29, 2024
501617b
Merge branch 'Qiskit:main' into add_random_circuit_with_graph
MozammilQ Jan 18, 2025
862e8c8
refactor code
MozammilQ Jan 19, 2025
2e29647
refactor code
MozammilQ Jan 19, 2025
4cfb770
fix lint
MozammilQ Jan 20, 2025
755ff33
optimized code; added a few tests
MozammilQ Jan 21, 2025
44cca81
fixed many typos; added suggestions by Eli Arbel
MozammilQ Jan 29, 2025
ebe1de0
added random circuit API docs
MozammilQ Jan 30, 2025
6e7ede8
fixed releasenote; added test to validate probs
MozammilQ Jan 31, 2025
eab2a4c
added support of cp_map; fixed lint
MozammilQ Jan 31, 2025
7cb79aa
Merge branch 'Qiskit:main' into add_random_circuit_with_graph
MozammilQ Feb 2, 2025
aabacb9
refactor code
MozammilQ Feb 5, 2025
7c1cca7
Merge branch 'main' into add_random_circuit_with_graph
MozammilQ Feb 5, 2025
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
300 changes: 259 additions & 41 deletions qiskit/circuit/random/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,52 +11,21 @@
# that they have been altered from the originals.

"""Utility functions for generating random circuits."""

import numpy as np

from qiskit.circuit import ClassicalRegister, QuantumCircuit, CircuitInstruction
from qiskit.circuit import (
ClassicalRegister,
QuantumCircuit,
CircuitInstruction,
Qubit,
QuantumRegister,
)
from qiskit.circuit import Reset
from qiskit.circuit.library import standard_gates
from qiskit.circuit.exceptions import CircuitError


def random_circuit(
num_qubits, depth, max_operands=4, measure=False, conditional=False, reset=False, seed=None
):
"""Generate random circuit of arbitrary size and form.

This function will generate a random circuit by randomly selecting gates
from the set of standard gates in :mod:`qiskit.circuit.library.standard_gates`. For example:

.. plot::
:include-source:

from qiskit.circuit.random import random_circuit

circ = random_circuit(2, 2, measure=True)
circ.draw(output='mpl')

Args:
num_qubits (int): number of quantum wires
depth (int): layers of operations (i.e. critical path length)
max_operands (int): maximum qubit operands of each gate (between 1 and 4)
measure (bool): if True, measure all qubits at the end
conditional (bool): if True, insert middle measurements and conditionals
reset (bool): if True, insert middle resets
seed (int): sets random seed (optional)

Returns:
QuantumCircuit: constructed circuit

Raises:
CircuitError: when invalid options given
"""
if num_qubits == 0:
return QuantumCircuit()
if max_operands < 1 or max_operands > 4:
raise CircuitError("max_operands must be between 1 and 4")
max_operands = max_operands if num_qubits > max_operands else num_qubits

def _get_gates():
gates_1q = [
# (Gate class, number of qubits, number of parameters)
(standard_gates.IGate, 1, 0),
Expand All @@ -80,8 +49,7 @@ def random_circuit(
(standard_gates.YGate, 1, 0),
(standard_gates.ZGate, 1, 0),
]
if reset:
gates_1q.append((Reset, 1, 0))

gates_2q = [
(standard_gates.CXGate, 2, 0),
(standard_gates.DCXGate, 2, 0),
Expand Down Expand Up @@ -118,6 +86,256 @@ def random_circuit(
(standard_gates.C3SXGate, 4, 0),
(standard_gates.RC3XGate, 4, 0),
]
return (gates_1q, gates_2q, gates_3q, gates_4q)


def random_circuit_with_graph(
interaction_graph,
MozammilQ marked this conversation as resolved.
Show resolved Hide resolved
depth,
max_operands=2,
measure=False,
conditional=False,
reset=False,
seed=None,
insert_1q_oper=False,
prob_conditional: float = 0.1,
):
"""Generate random circuit of arbitrary size and form which strictly respects
MozammilQ marked this conversation as resolved.
Show resolved Hide resolved
the interaction graph passed as argument.

This function will generate a random circuit by randomly selecting gates
from the set of standard gates in :mod:`qiskit.circuit.library.standard_gates`.
User can attach a float value indicating the probability of getting selected as a
metadata to the edge of the graph generated. Pass in None if the probability is
not available. Even if a single probability is None, this will turn off the probabilistic
selection of qubit-pair.

If :arg:`max_operands` is set to 2, then a 2Q gate is chosen at random and a qubit-pair is
also chosen at random based on the probability attached to the qubit-pair, this 2Q gate
is applied to that qubit-pair and the rest of the qubits are filled with 1Q operations
based on :arg:`insert_1q_oper` is set to True. This makes a single layer of the cirucit.

Example:

.. plot::
:include-source:

from qiskit.circuit.random.utils import random_circuit_with_graph
import rustworkx as rx
pydi_graph = rx.PyDiGraph()
pydi_graph.add_nodes_from(range(10))
cp_map = [
(0, 2, 0.1),
(1, 3, 0.15),
(2, 4, 0.15),
(3, 4, 0.1),
(5, 7, 0.13),
(4, 7, 0.07),
(7, 9, 0.1),
(5, 8, 0.1),
(6, 9, 0.1),
]

pydi_graph.add_edges_from(cp_map)
inter_graph = (pydi_graph, None, None, None)
qc = random_circuit_with_graph(inter_graph, depth=3)
qc.draw(output='mpl')

Args:
interaction_graph (int): Interaction Graph
MozammilQ marked this conversation as resolved.
Show resolved Hide resolved
depth (int): Minimum number of times every qubit-pair is must be used .
max_operands (int): maximum qubit operands of each gate (between 1 and 2)
measure (bool): if True, measure all qubits at the end
conditional (bool): if True, insert middle measurements and conditionals
reset (bool): if True, insert middle resets
seed (int): sets random seed (optional)
insert_1q_oper (bool): Insert 1Q operations to the qubits which are left after
applying a 2Q gate on the selected qubit-pair.
prob_conditional (float): Probability less than 1.0, this is used to control the
occurence of conditionals in the circuit.

Returns:
QuantumCircuit: constructed circuit

Raises:
CircuitError: when invalid options given
ValueError: when the probabilities for qubit-pairs are passed and they do not add to 1.0
and when a negative probability exists.
"""
pydi_graph, pydi_graph_node_map, _, _ = interaction_graph
MozammilQ marked this conversation as resolved.
Show resolved Hide resolved
edges_probs = pydi_graph.edges()
MozammilQ marked this conversation as resolved.
Show resolved Hide resolved

# Just a switch for the probability weighted selection of qubit-pairs.
# If any value of the probability is None or the whole probability list is None,
# then, the probability weighted selection of qubit-pair would be turned off.
prob_weighted_mapping = not None in edges_probs

if prob_weighted_mapping:
for prob in edges_probs:
if prob < 0:
raise ValueError("The probability should be positive")

if prob_weighted_mapping and not np.isclose(np.sum(edges_probs), 1.000, rtol=0.001):
raise ValueError(
"The sum of all the probabilities of a qubit-pair to be selected is not 1.0"
)

if pydi_graph_node_map is not None:
qubits = list(pydi_graph_node_map.keys())
else:
n_q = pydi_graph.num_nodes()
qubits = [Qubit(QuantumRegister(n_q, "q"), idx) for idx in pydi_graph.nodes()]
MozammilQ marked this conversation as resolved.
Show resolved Hide resolved
num_qubits = len(qubits)

if num_qubits == 0:
return QuantumCircuit()

if max_operands < 1 or max_operands > 2:
raise CircuitError("max_operands must be between 1 and 2")
MozammilQ marked this conversation as resolved.
Show resolved Hide resolved

max_operands = max_operands if num_qubits > max_operands else num_qubits

edge_list = list(pydi_graph.edge_list())
num_edges = len(edge_list)

if num_edges == 0 or max_operands == 1:
return random_circuit(
num_qubits=num_qubits,
depth=depth,
max_operands=1,
measure=measure,
conditional=conditional,
reset=reset,
seed=seed,
)

gates_1q, gates_2q, _, _ = _get_gates()

if reset:
gates_1q.append((Reset, 1, 0))

gates_2q = np.array(
gates_2q, dtype=[("class", object), ("num_qubits", np.int64), ("num_params", np.int64)]
)
gates_1q = np.array(gates_1q, dtype=gates_2q.dtype)

qc = QuantumCircuit(qubits)

if measure or conditional:
cr = ClassicalRegister(num_qubits, "c")
qc.add_register(cr)

if seed is None:
seed = np.random.randint(0, np.iinfo(np.int32).max)
rng = np.random.default_rng(seed)

qubits = np.array(qc.qubits, dtype=object, copy=True)

stop = False
layer_idx = 0
edges_used = {edge: 0 for edge in edge_list}

while not stop:

gate_2q_choice = rng.choice(gates_2q, size=1)[0]
control, target = tuple(
rng.choice(edge_list, size=1, p=edges_probs if prob_weighted_mapping else None)[0]
)
params = rng.uniform(0, 2 * np.pi, size=gate_2q_choice["num_params"])
oper_2q = gate_2q_choice["class"](*params)

if conditional and layer_idx != 0 and rng.random(size=1) < prob_conditional:
conditional_value = rng.integers(0, 1 << min(num_qubits, 63), size=1)
qc.measure(qc.qubits, cr)
oper_2q = oper_2q.c_if(cr, int(conditional_value[0]))
qc._append(
CircuitInstruction(operation=oper_2q, qubits=[qubits[control], qubits[target]])
)

else:
qc._append(
CircuitInstruction(operation=oper_2q, qubits=[qubits[control], qubits[target]])
)

if insert_1q_oper:
# Now, apply 1Q operations to the rest of the qubits for current layer
unused_qubits = set(range(num_qubits)) - {control, target}
gate_1q_choices = rng.choice(gates_1q, size=len(unused_qubits), replace=True)

# Getting an idea for the maximum number of parameters to be used.
cumsum_params = np.cumsum(gate_1q_choices["num_params"], dtype=np.int64)
parameters = rng.uniform(0, 2 * np.pi, size=cumsum_params[-1])

for qubit_idx in unused_qubits:
gate_1q = gate_1q_choices[0]
gate_1q_choices = gate_1q_choices[1:]
num_params = gate_1q["num_params"]
params = parameters[:num_params]
parameters = parameters[num_params:]
oper_1q = gate_1q["class"](*params)

if conditional and layer_idx != 0 and rng.random(size=1) < prob_conditional:
conditional_value = rng.integers(0, 1 << min(num_qubits, 63), size=1)
qc.measure(qc.qubits, cr)
oper_1q = oper_1q.c_if(cr, int(conditional_value[0]))
qc._append(CircuitInstruction(operation=oper_1q, qubits=[qubits[qubit_idx]]))
else:
qc._append(CircuitInstruction(operation=oper_1q, qubits=[qubits[qubit_idx]]))

edges_used[(control, target)] += 1
layer_idx += 1

# check if every edge has been used at-least depth number of times.
reached_depth = np.array(list(edges_used.values())) >= depth
if all(reached_depth):
stop = True

if measure:
qc.measure(qc.qubits, cr)

return qc


def random_circuit(
num_qubits, depth, max_operands=4, measure=False, conditional=False, reset=False, seed=None
):
"""Generate random circuit of arbitrary size and form.

This function will generate a random circuit by randomly selecting gates
from the set of standard gates in :mod:`qiskit.circuit.library.standard_gates`. For example:

.. plot::
:include-source:

from qiskit.circuit.random import random_circuit

circ = random_circuit(2, 2, measure=True)
circ.draw(output='mpl')

Args:
num_qubits (int): number of quantum wires
depth (int): layers of operations (i.e. critical path length)
max_operands (int): maximum qubit operands of each gate (between 1 and 4)
measure (bool): if True, measure all qubits at the end
conditional (bool): if True, insert middle measurements and conditionals
reset (bool): if True, insert middle resets
seed (int): sets random seed (optional)

Returns:
QuantumCircuit: constructed circuit

Raises:
CircuitError: when invalid options given
"""
if num_qubits == 0:
return QuantumCircuit()
if max_operands < 1 or max_operands > 4:
raise CircuitError("max_operands must be between 1 and 4")
max_operands = max_operands if num_qubits > max_operands else num_qubits
gates_1q, gates_2q, gates_3q, gates_4q = _get_gates()

if reset:
gates_1q.append((Reset, 1, 0))

gates = gates_1q.copy()
if max_operands >= 2:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
features_circuits:
- |
The function :func:`~qiskit.circuit.random.utils.random_circuit_with_graph` adds a new feature
of generating random circuits with interaction graph.
User can attach a float value indicating the probability of getting selected as a metadata to
the edge of the graph generated. Pass in None if the probability is not available.
Even if a single probability is None, this will turn off the probabilistic selection of qubit-pair.

Arguments accepted by the function are
:code:`interaction_graph` Takes in the interaction graph
:code:`max_number_qubit_used` Takes the maximum number of times a qubit should be used in the circuit.
:code:`max_operands` Take the maximum number of operands a gate should have. (Possible values are 1 or 2)
:code:`measure` Should the circuit have measure instruction (True or False)
:code:`conditional` Should the circuit have conditional operations. (True or False)
:code:`reset` Should the cirucit have reset instruciton (True or False)
:code:`seed` Seed for the random number generator.
:code:`insert_1Q_oper` Should the circuit have 1Q operations to the qubits which are left after applying a 2Q gate on the selected qubit-pair.
:code:`prob_conditional` A float value less than 1.0, this is used to control the occurence of conditionals in the circuit.

.. code-block:: python

from qiskit.circuit.random.utils import random_circuit_with_graph
import rustworkx as rx
pydi_graph = rx.PyDiGraph()
n_q = 10
pydi_graph.add_nodes_from(range(n_q))
cp_map = [(0, 2, 0.1), (1, 3, 0.15), (2, 4, 0.15), (3, 4, 0.1), (5, 7, 0.13), (4, 7, 0.07), (7, 9, 0.1), (5, 8, 0.1), (6, 9, 0.1)]
pydi_graph.add_edges_from(cp_map)
inter_graph = (pydi_graph, None, None, None)
qc = random_circuit_with_graph(inter_graph, depth = 2, measure = True, conditional=True, reset = True, seed=0, insert_1Q_oper = True)
qc.draw(output='mpl', scale = 0.75)

Loading
Loading