pip install qupython
Most quantum computing SDKs involve bit-level operations, assembly-like syntax, and no higher-level data structures. quPython makes quantum programs look more like Python code through two main main design decisions:
- Quantum programs are (decorated) Python functions, no separate circuit objects.
- Quantum operations are methods on the
Qubit
class.
Here's an example:
from qupython import Qubit, quantum
@quantum
def random_bit():
qubit = Qubit() # Allocate new qubit
qubit.h() # Mutate qubit
return qubit.measure() # Measure qubit to bool
The @quantum
decorator converts the function into a quantum function that can
be executed on a quantum computer. When you run random_bit
, quPython compiles
your function to a quantum program, executes it, and returns results.
>>> random_bit()
True
Qubits feel like standard Python objects, which makes it easier to organise quantum programs using classes and other Python features. The following example creates a simple logical qubit class. This shows some nice consequences of quPython's design decisions:
- The lower level qubits and operation are handled by the class
- Qubits and classical bits can be initialized in methods and scoped to those methods
- Methods return classical bit objects to be used in the program or returned to the user; no need to keep track of bit indices or registers.
from qupython import Qubit, quantum
from qupython.typing import BitPromise
class LogicalQubit:
"""
Simple logical qubit using the five-qubit code.
See https://en.wikipedia.org/wiki/Five-qubit_error_correcting_code
"""
def __init__(self):
"""
Create new logical qubit and initialize to logical |0>.
Uses initialization procedure from https://quantumcomputing.stackexchange.com/a/14449
"""
self.qubits = [Qubit() for _ in range(5)]
self.qubits[4].z()
for q in self.qubits[:4]:
q.h()
with q.as_control():
self.qubits[4].x()
for a, b in [(0,4),(0,1),(2,3),(1,2),(3,4)]:
with self.qubits[b].as_control():
self.qubits[a].z()
def measure(self) -> BitPromise:
"""
Measure logical qubit to single classical bit
"""
# Note the `out` qubit is scoped to this function
out = Qubit().h()
for q in self.qubits:
with out.as_control():
q.z()
return out.h().measure()
Here's how you'd use this class.
@quantum
def logical_qubit_demo() -> BitPromise:
q = LogicalQubit()
return q.measure()
>>> logical_qubit_demo()
False
See the Logical qubit example for a more complete class.
If you want, you can just use quPython to create Qiskit circuits with Pythonic
syntax (rather than the assembly-like syntax of qc.cx(0, 1)
in native
Qiskit).
# Compile using quPython
logical_qubit_demo.compile()
# Draw compiled Qiskit circuit
logical_qubit_demo.circuit.draw()
┌───┐
q_0: ┤ H ├────────────■────────■───────────■─────■───────────────
├───┤ │ │ │ │
q_1: ┤ H ├──■─────────┼────────┼──■──■──■──┼─────┼───────────────
├───┤ │ │ │ │ │ │ │ │
q_2: ┤ H ├──┼────■────┼────────┼──┼──■──┼──■──■──┼───────────────
├───┤┌─┴─┐┌─┴─┐┌─┴─┐┌───┐ │ │ │ │ │
q_3: ┤ Z ├┤ X ├┤ X ├┤ X ├┤ X ├─┼──■──■──┼─────┼──┼─────■─────────
├───┤└───┘└───┘└───┘└─┬─┘ │ │ │ │ │ │ ┌───┐┌─┐
q_4: ┤ H ├─────────────────┼───┼─────┼──■─────■──■──■──■─┤ H ├┤M├
├───┤ │ │ │ │ └───┘└╥┘
q_5: ┤ H ├─────────────────■───■─────■──────────────■──────────╫─
└───┘ ║
c: 1/══════════════════════════════════════════════════════════╩═
0
You can compile the function without executing it, optimize the circuit, execute it however you like, then use quPython to interpret the results.
from qiskit_aer.primitives import Sampler
qiskit_result = Sampler().run(logical_qubit_demo.circuit).result()
logical_qubit_demo.interpret_result(qiskit_result) # returns `False`