qupython.function

  1# Class for @quantum functions
  2
  3from typing import Callable
  4
  5import qiskit
  6from qiskit_aer.primitives import Sampler
  7from .construction import _get_promises, _construct_circuit
  8from .qubit import BitPromise
  9from .err_msg import ERR_MSG
 10
 11
 12class quPythonFunction:
 13    """
 14    A wrapper to compile a quantum circuit from a function's outputs, execute
 15    it, and resolve any `BitPromise` objects. You'll probably create these
 16    objects through through the `@quantum` decorator.
 17
 18    ```python
 19    from qupython import Qubit, quantum
 20
 21    @quantum
 22    def my_function():
 23        return Qubit().measure()
 24
 25    type(my_function)  # qupython.function.quPythonFunction
 26    ```
 27
 28    Calling an instance of this class will (in the following order):
 29    1. Run the function in the Python interpreter
 30    2. Intercept the output of the function and search for any `BitPromise` objects.
 31    3. Compile the Qiskit circuit needed to calculate the `BitPromise` objects.
 32    4. Execute the circuit on a quantum backend.
 33    5. Use the execution results to assign the `BitPromise.value` attributes, resolving the promises.
 34
 35    Split these steps up by calling methods on this object rather than
 36    calling it. The `compile` method does steps 1-3, but does not execute the
 37    circuit on a quantum backend. It stores the compiled
 38    [`qiskit.QuantumCircuit`](https://docs.quantum.ibm.com/api/qiskit/qiskit.circuit.QuantumCircuit)
 39    in the `circuit` attribute. 
 40
 41    ```python
 42    my_function.compile()
 43    my_function.circuit.draw()
 44    ```
 45
 46    ```output
 47         ┌─┐
 48      q: ┤M├
 49         └╥┘
 50    c: 1/═╩═
 51          0
 52    ```
 53
 54    The `run` method carries out steps 4-5 using the circuit from the `circuit`
 55    attribute. If you replace the attribute with a different circuit (such as
 56    an optimized circuit), it will use that.
 57
 58    ```python
 59    my_function.run()  # BitPromise with value False
 60    ```
 61
 62    The `interpret_result` method uses a `SamplerResult` of the compiled
 63    circuit to resolve `BitPromises` and return the function output. Use this
 64    if you want to control the circuit execution.
 65
 66    ```python
 67    from qiskit_aer.primitives import Sampler
 68    qiskit_result = Sampler().run(my_function.circuit).result()
 69    my_function.interpret_result(qiskit_result)  # BitPromise with value False
 70    ```
 71
 72    """
 73    def __init__(self, function: Callable):
 74        self.function: Callable = function
 75        """
 76        The function to be compiled. It should return `BitPromise` objects.
 77        """
 78        self.circuit: qiskit.QuantumCircuit | None = None
 79        """
 80        The compiled Qiskit circuit. This is `None` until the class is
 81        called or `compile` is run.
 82        """
 83        self.promises: list[BitPromise] = None
 84        """The `BitPromise` objects to be resolved"""
 85
 86    def __call__(self, *args, **kwargs):
 87        """
 88        Run the function, compile the circuit, execute the circuit, and return
 89        the results.
 90        """
 91        self.compile(*args, **kwargs)
 92        return self.run()
 93
 94    def compile(self, *args, **kwargs):
 95        """
 96        Run function (with args and keyword args), collect promises, and
 97        compile circuit, but don't execute the circuit. Stores the compiled
 98        circuit in the `circuit` attribute.
 99        """
100        self.output = self.function(*args, **kwargs)
101        self.promises = list(_get_promises(self.output))
102        self.circuit = _construct_circuit(self.promises)
103
104    def run(self):
105        """
106        Run the pre-compiled circuit, interpret the result, and return the
107        output object with fulfilled promises.
108        """
109        if self.promises is None:
110            raise quPythonFunctionError(ERR_MSG["CompileFunctionBeforeRunning"])
111        if self.promises == []:
112            return self.output
113        self.sampler_result = Sampler().run(self.circuit).result()
114        return self.interpret_result(self.sampler_result)
115
116    def interpret_result(self, result: qiskit.primitives.SamplerResult):
117        """
118        Given a one-shot `SamplerResult`, parse the result and return the
119        function's output with resolved promises.
120        """
121        if result is None and self.promises == []:
122            # Function is not quantum
123            return self.output
124
125        integer = [*result.quasi_dists[0]][0]  # Get 0th key from dict
126        for promise in self.promises:
127            promise.value = bool((1 << promise.index) & integer)
128        return self.output
129
130class quPythonFunctionError(Exception):
131    pass
class quPythonFunction:
 13class quPythonFunction:
 14    """
 15    A wrapper to compile a quantum circuit from a function's outputs, execute
 16    it, and resolve any `BitPromise` objects. You'll probably create these
 17    objects through through the `@quantum` decorator.
 18
 19    ```python
 20    from qupython import Qubit, quantum
 21
 22    @quantum
 23    def my_function():
 24        return Qubit().measure()
 25
 26    type(my_function)  # qupython.function.quPythonFunction
 27    ```
 28
 29    Calling an instance of this class will (in the following order):
 30    1. Run the function in the Python interpreter
 31    2. Intercept the output of the function and search for any `BitPromise` objects.
 32    3. Compile the Qiskit circuit needed to calculate the `BitPromise` objects.
 33    4. Execute the circuit on a quantum backend.
 34    5. Use the execution results to assign the `BitPromise.value` attributes, resolving the promises.
 35
 36    Split these steps up by calling methods on this object rather than
 37    calling it. The `compile` method does steps 1-3, but does not execute the
 38    circuit on a quantum backend. It stores the compiled
 39    [`qiskit.QuantumCircuit`](https://docs.quantum.ibm.com/api/qiskit/qiskit.circuit.QuantumCircuit)
 40    in the `circuit` attribute. 
 41
 42    ```python
 43    my_function.compile()
 44    my_function.circuit.draw()
 45    ```
 46
 47    ```output
 48         ┌─┐
 49      q: ┤M├
 50         └╥┘
 51    c: 1/═╩═
 52          0
 53    ```
 54
 55    The `run` method carries out steps 4-5 using the circuit from the `circuit`
 56    attribute. If you replace the attribute with a different circuit (such as
 57    an optimized circuit), it will use that.
 58
 59    ```python
 60    my_function.run()  # BitPromise with value False
 61    ```
 62
 63    The `interpret_result` method uses a `SamplerResult` of the compiled
 64    circuit to resolve `BitPromises` and return the function output. Use this
 65    if you want to control the circuit execution.
 66
 67    ```python
 68    from qiskit_aer.primitives import Sampler
 69    qiskit_result = Sampler().run(my_function.circuit).result()
 70    my_function.interpret_result(qiskit_result)  # BitPromise with value False
 71    ```
 72
 73    """
 74    def __init__(self, function: Callable):
 75        self.function: Callable = function
 76        """
 77        The function to be compiled. It should return `BitPromise` objects.
 78        """
 79        self.circuit: qiskit.QuantumCircuit | None = None
 80        """
 81        The compiled Qiskit circuit. This is `None` until the class is
 82        called or `compile` is run.
 83        """
 84        self.promises: list[BitPromise] = None
 85        """The `BitPromise` objects to be resolved"""
 86
 87    def __call__(self, *args, **kwargs):
 88        """
 89        Run the function, compile the circuit, execute the circuit, and return
 90        the results.
 91        """
 92        self.compile(*args, **kwargs)
 93        return self.run()
 94
 95    def compile(self, *args, **kwargs):
 96        """
 97        Run function (with args and keyword args), collect promises, and
 98        compile circuit, but don't execute the circuit. Stores the compiled
 99        circuit in the `circuit` attribute.
100        """
101        self.output = self.function(*args, **kwargs)
102        self.promises = list(_get_promises(self.output))
103        self.circuit = _construct_circuit(self.promises)
104
105    def run(self):
106        """
107        Run the pre-compiled circuit, interpret the result, and return the
108        output object with fulfilled promises.
109        """
110        if self.promises is None:
111            raise quPythonFunctionError(ERR_MSG["CompileFunctionBeforeRunning"])
112        if self.promises == []:
113            return self.output
114        self.sampler_result = Sampler().run(self.circuit).result()
115        return self.interpret_result(self.sampler_result)
116
117    def interpret_result(self, result: qiskit.primitives.SamplerResult):
118        """
119        Given a one-shot `SamplerResult`, parse the result and return the
120        function's output with resolved promises.
121        """
122        if result is None and self.promises == []:
123            # Function is not quantum
124            return self.output
125
126        integer = [*result.quasi_dists[0]][0]  # Get 0th key from dict
127        for promise in self.promises:
128            promise.value = bool((1 << promise.index) & integer)
129        return self.output

A wrapper to compile a quantum circuit from a function's outputs, execute it, and resolve any BitPromise objects. You'll probably create these objects through through the @quantum decorator.

from qupython import Qubit, quantum

@quantum
def my_function():
    return Qubit().measure()

type(my_function)  # qupython.function.quPythonFunction

Calling an instance of this class will (in the following order):

  1. Run the function in the Python interpreter
  2. Intercept the output of the function and search for any BitPromise objects.
  3. Compile the Qiskit circuit needed to calculate the BitPromise objects.
  4. Execute the circuit on a quantum backend.
  5. Use the execution results to assign the BitPromise.value attributes, resolving the promises.

Split these steps up by calling methods on this object rather than calling it. The compile method does steps 1-3, but does not execute the circuit on a quantum backend. It stores the compiled qiskit.QuantumCircuit in the circuit attribute.

my_function.compile()
my_function.circuit.draw()
     ┌─┐
  q: ┤M├
     └╥┘
c: 1/═╩═
      0

The run method carries out steps 4-5 using the circuit from the circuit attribute. If you replace the attribute with a different circuit (such as an optimized circuit), it will use that.

my_function.run()  # BitPromise with value False

The interpret_result method uses a SamplerResult of the compiled circuit to resolve BitPromises and return the function output. Use this if you want to control the circuit execution.

from qiskit_aer.primitives import Sampler
qiskit_result = Sampler().run(my_function.circuit).result()
my_function.interpret_result(qiskit_result)  # BitPromise with value False
quPythonFunction(function: Callable)
74    def __init__(self, function: Callable):
75        self.function: Callable = function
76        """
77        The function to be compiled. It should return `BitPromise` objects.
78        """
79        self.circuit: qiskit.QuantumCircuit | None = None
80        """
81        The compiled Qiskit circuit. This is `None` until the class is
82        called or `compile` is run.
83        """
84        self.promises: list[BitPromise] = None
85        """The `BitPromise` objects to be resolved"""
function: Callable

The function to be compiled. It should return BitPromise objects.

circuit: qiskit.circuit.quantumcircuit.QuantumCircuit | None

The compiled Qiskit circuit. This is None until the class is called or compile is run.

promises: list[qupython.qubit.BitPromise]

The BitPromise objects to be resolved

def compile(self, *args, **kwargs):
 95    def compile(self, *args, **kwargs):
 96        """
 97        Run function (with args and keyword args), collect promises, and
 98        compile circuit, but don't execute the circuit. Stores the compiled
 99        circuit in the `circuit` attribute.
100        """
101        self.output = self.function(*args, **kwargs)
102        self.promises = list(_get_promises(self.output))
103        self.circuit = _construct_circuit(self.promises)

Run function (with args and keyword args), collect promises, and compile circuit, but don't execute the circuit. Stores the compiled circuit in the circuit attribute.

def run(self):
105    def run(self):
106        """
107        Run the pre-compiled circuit, interpret the result, and return the
108        output object with fulfilled promises.
109        """
110        if self.promises is None:
111            raise quPythonFunctionError(ERR_MSG["CompileFunctionBeforeRunning"])
112        if self.promises == []:
113            return self.output
114        self.sampler_result = Sampler().run(self.circuit).result()
115        return self.interpret_result(self.sampler_result)

Run the pre-compiled circuit, interpret the result, and return the output object with fulfilled promises.

def interpret_result(self, result: qiskit.primitives.base.sampler_result.SamplerResult):
117    def interpret_result(self, result: qiskit.primitives.SamplerResult):
118        """
119        Given a one-shot `SamplerResult`, parse the result and return the
120        function's output with resolved promises.
121        """
122        if result is None and self.promises == []:
123            # Function is not quantum
124            return self.output
125
126        integer = [*result.quasi_dists[0]][0]  # Get 0th key from dict
127        for promise in self.promises:
128            promise.value = bool((1 << promise.index) & integer)
129        return self.output

Given a one-shot SamplerResult, parse the result and return the function's output with resolved promises.

class quPythonFunctionError(builtins.Exception):
131class quPythonFunctionError(Exception):
132    pass

Common base class for all non-exit exceptions.

Inherited Members
builtins.Exception
Exception
builtins.BaseException
with_traceback
add_note
args