Loop through the Rust bytecode iterator `bytecode` producing a :class:`~qiskit.circuit.QuantumCircuit` instance from it. All the hard work is done in Rust space where operations are faster; here, we're just about looping through the instructions as fast as possible, doing as little calc
(bytecode, custom_instructions: Iterable[CustomInstruction])
| 203 | |
| 204 | |
| 205 | def from_bytecode(bytecode, custom_instructions: Iterable[CustomInstruction]): |
| 206 | """Loop through the Rust bytecode iterator `bytecode` producing a |
| 207 | :class:`~qiskit.circuit.QuantumCircuit` instance from it. All the hard work is done in Rust |
| 208 | space where operations are faster; here, we're just about looping through the instructions as |
| 209 | fast as possible, doing as little calculation as we can in Python space. The Python-space |
| 210 | components are the vast majority of the runtime. |
| 211 | |
| 212 | The "bytecode", and so also this Python function, is very tightly coupled to the output of the |
| 213 | Rust parser. The bytecode itself is largely defined by Rust; from Python space, the iterator is |
| 214 | over essentially a 2-tuple of `(opcode, operands)`. The `operands` are fixed by Rust, and |
| 215 | assumed to be correct by this function. |
| 216 | |
| 217 | The Rust code is responsible for all validation. If this function causes any errors to be |
| 218 | raised by Qiskit (except perhaps for some symbolic manipulations of `Parameter` objects), we |
| 219 | should consider that a bug in the Rust code.""" |
| 220 | # The method `QuantumCircuit._append` is a semi-public method, so isn't really subject to |
| 221 | # "protected access". |
| 222 | |
| 223 | qc = QuantumCircuit() |
| 224 | qubits = [] |
| 225 | clbits = [] |
| 226 | gates = [] |
| 227 | has_u, has_cx = False, False |
| 228 | for custom in custom_instructions: |
| 229 | gates.append(custom.constructor) |
| 230 | if custom.name == "U": |
| 231 | has_u = True |
| 232 | elif custom.name == "CX": |
| 233 | has_cx = True |
| 234 | if not has_u: |
| 235 | gates.append(lib.UGate) |
| 236 | if not has_cx: |
| 237 | gates.append(lib.CXGate) |
| 238 | # Pull this out as an explicit iterator so we can manually advance the loop in `DeclareGate` |
| 239 | # contexts easily. |
| 240 | bc = iter(bytecode) |
| 241 | for op in bc: |
| 242 | # We have to check `op.opcode` so many times, it's worth pulling out the extra attribute |
| 243 | # access. We should check the opcodes in order of their likelihood to be in the OQ2 program |
| 244 | # for speed. Gate applications are by far the most common for long programs. This function |
| 245 | # is deliberately long and does not use hashmaps or function lookups for speed in |
| 246 | # Python-space. |
| 247 | opcode = op.opcode |
| 248 | # `OpCode` is an `enum` in Rust, but its instances don't have the same singleton property as |
| 249 | # Python `enum.Enum` objects. |
| 250 | if opcode == OpCode.Gate: |
| 251 | gate_id, parameters, op_qubits = op.operands |
| 252 | qc._append( |
| 253 | CircuitInstruction(gates[gate_id](*parameters), [qubits[q] for q in op_qubits]) |
| 254 | ) |
| 255 | elif opcode == OpCode.ConditionedGate: |
| 256 | gate_id, parameters, op_qubits, creg, value = op.operands |
| 257 | with qc.if_test((qc.cregs[creg], value)): |
| 258 | gate = gates[gate_id](*parameters) |
| 259 | qc.append(gate, [qubits[q] for q in op_qubits]) |
| 260 | elif opcode == OpCode.Measure: |
| 261 | qubit, clbit = op.operands |
| 262 | qc._append(CircuitInstruction(Measure(), (qubits[qubit],), (clbits[clbit],))) |
nothing calls this directly
no test coverage detected