-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Add random circuit with graph #12474
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
Conversation
Thank you for opening a new pull request. Before your PR can be merged it will first need to pass continuous integration tests and be reviewed. Sometimes the review process can be slow, so please be patient. While you're waiting, please feel free to review other open PRs. While only a subset of people are authorized to approve pull requests for merging, everyone is encouraged to review open pull requests. Doing reviews helps reduce the burden on the core team and helps make the project's code better for everyone. One or more of the following people are relevant to this code:
|
Pull Request Test Coverage Report for Build 14320951686Details
💛 - Coveralls |
Dear @MozammilQ thanks for showing interest in this issue. I will be on vacation and look into this PR next week! In the meantime make yourself familiar with https://github.com/Qiskit/qiskit/blob/main/CONTRIBUTING.md and the unitaryhack rules (if you have not already). :-) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added the suggestions :)
@sbrandhsn , please see if this is good enough :) |
@MozammilQ thanks for your interest in looking at issue #12364 ! I agree this issue is a bit tricky but I think your contribution is going into the right direction. :-) Also, thanks for exploring different options for that issue! I did not have time yet to look into your code but wanted to spend the time to clarify some of your questions. To give a little bit of context, the output of this procedure will likely not be executed directly on a quantum computer but will prove valuable for investigating compiler capabilities and efficiencies. The problem I wanted to fix with the You rightfully noted that the generated circuits may become quite large but I think this is fine. I suspect that if you have I think it would be a good idea to raise an error with a reasonable error message if some of the edge weights are none while others are not none. If all edge weights are none, you can assume the weight of each edge to be 1/N. With respects to resets and conditional operations, you can simply follow the approach given in the original Some of your 'reviewer markers' should actually be comments in your code, it would be great if you add those as you see fit. :-) I think the implementation should eventually consider the edge weight as well as consider the I like the idea to fill unused qubits in a layer with a single-qubit gate if Let me know if things became clearer and whether you require any other information. Also, if you make modifications to your PR, let me know for which implementation you went for eventually. :-) |
I have already worked a lot on this PR, so I will surely do some modifications to the code, but, actually right now bit busy in "IBM Quantum Challenge 2024" @sbrandhsn , you explained it very very well, thanks a lot for the time you gave to write and explain these. |
Absolutely! :-) |
This is python so, focus is on code readability. @sbrandhsn , please have a look, and let me know what to change. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @MozammilQ, this already looks like it is in good shape! :-) I left a couple of comments. I also think there should be a couple of more test cases with different types of interaction graphs such as sparse graphs, (almost) fully connected and a couple more random ones. Also, I think it would be best if you reused the code in other test cases instead of copying them. See https://networkx.org/documentation/stable/reference/generators.html for how to generate these graphs.
releasenotes/notes/Added-random-circuit-from-graph-95c22eeabdea89d0.yaml
Outdated
Show resolved
Hide resolved
releasenotes/notes/Added-random-circuit-from-graph-95c22eeabdea89d0.yaml
Outdated
Show resolved
Hide resolved
for wire in dag.wires: | ||
for dag_op_node in dag.nodes_on_wire(wire, only_ops=True): | ||
if dag_op_node.op.num_qubits == 2: | ||
control, target = dag_op_node.qargs | ||
control_idx = control._index | ||
target_idx = target._index | ||
cp_mp.update({(control_idx, target_idx)}) | ||
|
||
# make sure every qubit-pair from the circuit actually present in the edge_list | ||
for cp in cp_mp: | ||
self.assertTrue(cp in edge_list) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can replace this chunk of code by build_interaction_graph
in https://github.com/Qiskit/qiskit/blob/main/qiskit/transpiler/passes/layout/vf2_utils.py and a subgraph isomorphism check. :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have kept the logic untouched, because for some reasons build_interaction_graph
is giving out a coupling map, which is not correct for the given circuit.
On visual inspection, it was confirmed that the coupling map given out by this logic is indeed correct and serve the purpose.
@eliarbel , I have done with the changes, please do have a look :)
The test has been added in 6e7ede8 Below is the performance comparison :) from qiskit.circuit.random import random_circuit_from_graph, random_circuit
import numpy as np
import rustworkx as rx
import time
distance = 5
h_h_g = rx.generators.directed_heavy_hex_graph(d=distance, bidirectional = False)
seed = 32434
rng = np.random.default_rng(seed = seed)
cp_map_list = []
edge_list = h_h_g.edge_list()
# generating a non-normalized list of integers.
random_probs = rng.choice(range(10, 25), size = len(edge_list)).tolist()
sum_probs = sum(random_probs)
for idx, qubits in enumerate(edge_list):
ctrl, trgt = qubits
cp_map_list.append((ctrl, trgt, random_probs[idx]))
h_h_g.clear_edges()
h_h_g.add_edges_from(cp_map_list)
start_time = time.time()
qc = random_circuit_from_graph(h_h_g,
min_2q_gate_per_edge = 10,
max_operands = 2,
measure = True,
conditional = True, # Just making it a bit more challenging.
reset = True,
seed = seed,
insert_1q_oper = True,
prob_conditional = 0.91,
prob_reset = 0.50)
end_time = time.time()
count_oper = sum(qc.count_ops().values())
print(f"For `random_circuit_from_graph`:\nTime taken: {end_time - start_time} to generate {count_oper} operations")
print(f"Time taken per operation: {(end_time-start_time)/count_oper*1000000} us\n", 40*'#', '\n')
rand_start_time = time.time()
qc_rand = random_circuit(num_qubits = 70, depth = 299, max_operands = 2, measure = True, conditional = True, reset = True, seed = seed)
rand_end_time = time.time()
count_rand_oper = sum(qc_rand.count_ops().values())
print(f"For `random_circuit`:\nTime taken: {rand_end_time-rand_start_time} to generate {count_rand_oper} operations")
print(f"Time taken per operation: {(rand_end_time-rand_start_time)/count_rand_oper*1000000} us") Output
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @MozammilQ for addressing the comments in the previous review, I think it was a big step forward. Now with random_circuit
almost back to its original state, I gave a more thorough look at the other parts. Please see my line comments. I hope this will be the last round of changes before we can merge the PR.
Regarding your performance comparison, it was helpful. It does help with getting a rough sense for the scaling of random_circuit_from_graph
compared to random_circuit
. If we want at some point to get a better understanding of it, we'll need to explore the parameter space more rigorously. For example, I played with your code a bit and got this:
qc = random_circuit_from_graph(h_h_g,
min_2q_gate_per_edge = 90,
max_operands = 2,
measure = True,
conditional = False, # Just making it a bit more challenging.
reset = False,
seed = seed,
insert_1q_oper = True)
qc_rand = random_circuit(num_qubits = 70, depth = 298, max_operands = 2, measure = True, conditional = False, reset = False, seed = seed)
# For `random_circuit_from_graph`:
# Time taken: 1.6271507740020752 to generate 19729 operations
# Time taken per operation: 82.47507597962772 us
# ########################################
# For `random_circuit`:
# Time taken: 0.5127830505371094 to generate 19067 operations
# Time taken per operation: 26.893745766880443 us
But still it's good to have some sense that the new function is not too far off. I've left a couple optimization suggestions that might help but I wouldn't go beyond that in this PR trying to optimize performance more. We can handle this aspect in a new PR if there will be a use-case for which performance becomes a bottleneck.
releasenotes/notes/Added-random-circuit-from-graph-95c22eeabdea89d0.yaml
Outdated
Show resolved
Hide resolved
releasenotes/notes/Added-random-circuit-from-graph-95c22eeabdea89d0.yaml
Outdated
Show resolved
Hide resolved
@@ -75,13 +80,14 @@ def test_random_mid_circuit_measure_conditional(self): | |||
circ = random_circuit(num_qubits, depth, conditional=True, seed=16) | |||
self.assertEqual(circ.width(), 2 * num_qubits) | |||
op_names = [instruction.operation.name for instruction in circ] | |||
self.assertEqual(4, len(op_names)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What changed in random_circuit
that requires this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Referring to test.python.circuit.test_random_circuit.TestCircuitRandom.test_random_mid_circuit_measure_conditional
qc.append
leads to the following result.
For this code:
from qiskit.circuit.random import random_circuit
num_qubits = depth = 2
circ = random_circuit(num_qubits, depth, conditional=True, seed=16)
for instr in circ._data:
print(instr)
CircuitInstruction(operation=Instruction(name='ry', num_qubits=1, num_clbits=0, params=[np.float64(3.9050564582327985)]), qubits=(<Qubit register=(2, "q"), index=0>,), clbits=())
CircuitInstruction(operation=Instruction(name='measure', num_qubits=1, num_clbits=1, params=[]), qubits=(<Qubit register=(2, "q"), index=0>,), clbits=(<Clbit register=(2, "c"), index=0>,))
CircuitInstruction(operation=Instruction(name='measure', num_qubits=1, num_clbits=1, params=[]), qubits=(<Qubit register=(2, "q"), index=1>,), clbits=(<Clbit register=(2, "c"), index=1>,))
CircuitInstruction(operation=Instruction(name='if_else', num_qubits=2, num_clbits=2, params=[<qiskit.circuit.quantumcircuit.QuantumCircuit object at 0x7d9dfe398710>, None]), qubits=(<Qubit register=(2, "q"), index=1>, <Qubit register=(2, "q"), index=0>), clbits=(<Clbit register=(2, "c"), index=0>, <Clbit register=(2, "c"), index=1>))
circ.draw(output='mpl')
len(op_names)
Gives: 4
Now, qc._append
on the same code
For this code:
from qiskit.circuit.random import random_circuit
num_qubits = depth = 2
circ = random_circuit(num_qubits, depth, conditional=True, seed=16)
for instr in circ._data:
print(instr)
CircuitInstruction(operation=Instruction(name='ry', num_qubits=1, num_clbits=0, params=[np.float64(3.9050564582327985)]), qubits=(<Qubit register=(2, "q"), index=0>,), clbits=())
CircuitInstruction(operation=Instruction(name='measure', num_qubits=1, num_clbits=1, params=[]), qubits=(<Qubit register=(2, "q"), index=0>,), clbits=(<Clbit register=(2, "c"), index=0>,))
CircuitInstruction(operation=Instruction(name='measure', num_qubits=1, num_clbits=1, params=[]), qubits=(<Qubit register=(2, "q"), index=1>,), clbits=(<Clbit register=(2, "c"), index=1>,))
CircuitInstruction(operation=Instruction(name='cs', num_qubits=2, num_clbits=0, params=[]), qubits=(<Qubit register=(2, "q"), index=1>, <Qubit register=(2, "q"), index=0>), clbits=())
CircuitInstruction(operation=Instruction(name='if_else', num_qubits=0, num_clbits=2, params=[<qiskit.circuit.quantumcircuit.QuantumCircuit object at 0x704da052f470>, None]), qubits=(), clbits=(<Clbit register=(2, "c"), index=0>, <Clbit register=(2, "c"), index=1>))
Noticed, the problem?
for instr in circ._data:
print(instr.operation.num_qubits)
Gives
1
1
1
2
0
circ.draw(output='mpl')
len(op_names)
Gives: 5
The above observation suggests that may be not in the future but at present we should go with qc.append
.
Regarding the speed, qc.append
looks ~x2 slower than qc._append
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah... this was a good catch! So QuantumCircuit._append
could not be used within a control-flow builder context (e.g. within with qc.if_test
) since it doesn't look at the current circuit scope, effectively ignoring the control-flow scope here. This is why we don't see the if test surrounding the CS gate.
So let's keep the qc.append
inside all the control-flow build contexts here and use qc._append
elsewhere. You can also do this in random_circuit_with_graph
for an enhanced performance.
Regarding performance code in the comment After change in the logic comment After building rust in release mode from qiskit.circuit.random import random_circuit_from_graph, random_circuit
import numpy as np
import rustworkx as rx
import time
distance = 17
h_h_g = rx.generators.directed_heavy_hex_graph(d=distance, bidirectional = False)
seed = 32434
rng = np.random.default_rng(seed = seed)
cp_map_list = []
edge_list = h_h_g.edge_list()
# generating a non-normalized list of integers.
random_probs = rng.choice(range(10, 25), size = len(edge_list)).tolist()
sum_probs = sum(random_probs)
for idx, qubits in enumerate(edge_list):
ctrl, trgt = qubits
cp_map_list.append((ctrl, trgt, random_probs[idx]))
h_h_g.clear_edges()
h_h_g.add_edges_from(cp_map_list)
start_time = time.time()
qc = random_circuit_from_graph(h_h_g,
min_2q_gate_per_edge = 200,
max_operands = 2,
measure = True,
conditional = False,
reset = False,
seed = seed,
insert_1q_oper = True)
end_time = time.time()
count_oper = sum(qc.count_ops().values())
print(f"For `random_circuit_from_graph`:\nTime taken: {end_time - start_time} to generate {count_oper} operations")
print(f"Time taken per operation: {(end_time-start_time)/count_oper*1000000} us\n", 40*'#', '\n')
rand_start_time = time.time()
qc_rand = random_circuit(num_qubits = 600, depth = 620, max_operands = 2, measure = True, conditional = False, reset = False, seed = seed)
rand_end_time = time.time()
count_rand_oper = sum(qc_rand.count_ops().values())
print(f"For `random_circuit`:\nTime taken: {rand_end_time-rand_start_time} to generate {count_rand_oper} operations")
print(f"Time taken per operation: {(rand_end_time-rand_start_time)/count_rand_oper*1000000} us")
For logic change see comment Please, see if the changes are good enough :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @MozammilQ for making all the changes. Looks good!
Regarding the qc.append
comment, please see my response here. Let's apply it and we're done.
@eliarbel , I was curious enough to update the branch, I hope I didn't mess up anything :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is ready, thanks @MozammilQ for your contribution!
All comments by @brandhsn have been addressed but one regarding some unit test, which seems not critical for blocking this PR from being merged.
Summary
The function
qiskit.circuit.random.utils.random_circuit_from_graph
adds a new featureof generating random circuits with interaction graph.
Details and comments