qrisp.QuantumSession.compile#

QuantumSession.compile(workspace=0, intended_measurements=[], cancel_qfts=True, disable_uncomputation=True)[source]#

Method to compile the QuantumSession into a QuantumCircuit. The compiler dynamically allocates the qubits of the QuantumSession on qubits that might have been used by priorly deleted QuantumVariables.

Using the workspace keyword, we can grant the compiler a number of extra qubits to use in order to reduce the circuit depth.

Furthermore, the compiler recompiles any mcx instruction with method = auto using a dynamically generated mcx implementation that makes use of as much of the currently available clean and dirty ancillae. This feature will never allocate additional qubits on its own. If required, it can be supplied with additional space using the workspace keyword.

The .compile method is called by default, when executing the get_measurement method of QuantumVariable. This method also allows specification of compilation option through the compilation_kwargs argument.

Parameters
workspaceint, optional

The amount of workspace qubits to be granted. The default is 0.

intended_measurementslist[Qubit], optional

A list of Qubits that are supposed to be measured. The compiler will remove any instructions that are not directly neccessary to perform the measurements. Note that the resulting QuantumCircuit contains no measurements, such that the user can still specify a classical bit for the measurement. The default ist [].

cancel_qftsbool, optional

If set to True, any QFT instruction that is executed on a set of qubits that have just been allocated (ie. the \(\ket{0}\) state) will be replaced by a set of H gates. The same goes for QFT instructions executed directly before deallocation. The default is True.

disable_uncomputationbool, optional

Experimental feature the allows fully automized uncomputation. If set to False any QuantumVariable that went out of scope will be uncomputed by the compiler. The default is True.

Returns
QuantumCircuit

The compiled QuantumCircuit.

Examples

Workspace

We calculate a product of 2 QuantumFloats using the sbp_mult function which heavily profits from more workspace.

>>> from qrisp import QuantumFloat, sbp_mult
>>> qf_0 = QuantumFloat(5)
>>> qf_0[:] = 3
>>> qf_1 = QuantumFloat(5)
>>> qf_1[:] = 5

Calculate product:

>>> qf_res = sbp_mult(qf_0, qf_1)
>>> qf_res.qs.num_qubits()
45

Compile circuit with no workspace

>>> qc_0 = qf_res.qs.compile(0)
>>> qc_0.num_qubits()
21
>>> qc_0.depth()
497

Compile circuit with 4 workspace qubits

>>> qc_1 = qf_res.qs.compile(4)
>>> qc_1.num_qubits()
25
>>> qc_1.depth()
258

mcx recompilation

To demonstrate the recompilation feature, we create two QuantumVariables.

>>> from qrisp import QuantumVariable, mcx, cx
>>> ctrl = QuantumVariable(4)
>>> target = QuantumVariable(1)
>>> mcx(ctrl, target)
>>> print(ctrl.qs)
QuantumCircuit:
--------------
  ctrl.0: ──■──
            │
  ctrl.1: ──■──
            │
  ctrl.2: ──■──
            │
  ctrl.3: ──■──
          ┌─┴─┐
target.0: ┤ X ├
          └───┘
Live QuantumVariables:
---------------------
QuantumVariable ctrl
QuantumVariable target

We can now call the .compile method

>>> compiled_qc = ctrl.qs.compile()
>>> compiled_qc.depth()
50
>>> print(compiled_qc)
  ctrl.0: ──■──
            │
  ctrl.1: ──■──
            │
  ctrl.2: ──■──
            │
  ctrl.3: ──■──
          ┌─┴─┐
target.0: ┤ X ├
          └───┘

We see no change here, because there was no free space to execute a more optimal mcx implementation. We can grant additional space using the workspace argument:

>>> compiled_qc = ctrl.qs.compile(workspace = 2)
>>> compiled_qc.depth()
22
>>> print(compiled_qc)
             ┌────────┐               ┌────────┐
     ctrl.0: ┤0       ├───────────────┤0       ├──────────
             │        │               │        │
     ctrl.1: ┤1       ├───────────────┤1       ├──────────
             │        │┌────────┐     │        │┌────────┐
     ctrl.2: ┤        ├┤0       ├─────┤        ├┤0       ├
             │  pt2cx ││        │     │  pt2cx ││        │
     ctrl.3: ┤        ├┤1       ├─────┤        ├┤1       ├
             │        ││        │┌───┐│        ││        │
   target.0: ┤        ├┤  pt2cx ├┤ X ├┤        ├┤  pt2cx ├
             │        ││        │└─┬─┘│        ││        │
workspace_0: ┤2       ├┤        ├──■──┤2       ├┤        ├
             └────────┘│        │  │  └────────┘│        │
workspace_1: ──────────┤2       ├──■────────────┤2       ├
                       └────────┘               └────────┘

Granting extra qubits to use this feature is however not usually necessary. The compiler automatically detects and reuses qubit resources available at the corresponding stage of the compilation. To demonstrate this feature, we allocate a third QuantumVariable:

>>> qv = QuantumVariable(2)
>>> cx(target[0], qv)
>>> print(ctrl.qs.compile())
          ┌────────┐               ┌────────┐
  ctrl.0: ┤0       ├───────────────┤0       ├────────────────────
          │        │               │        │
  ctrl.1: ┤1       ├───────────────┤1       ├────────────────────
          │        │┌────────┐     │        │┌────────┐
  ctrl.2: ┤        ├┤0       ├─────┤        ├┤0       ├──────────
          │  pt2cx ││        │     │  pt2cx ││        │
  ctrl.3: ┤        ├┤1       ├─────┤        ├┤1       ├──────────
          │        ││        │┌───┐│        ││        │
target.0: ┤        ├┤  pt2cx ├┤ X ├┤        ├┤  pt2cx ├──■────■──
          │        ││        │└─┬─┘│        ││        │┌─┴─┐  │
    qv.0: ┤2       ├┤        ├──■──┤2       ├┤        ├┤ X ├──┼──
          └────────┘│        │  │  └────────┘│        │└───┘┌─┴─┐
    qv.1: ──────────┤2       ├──■────────────┤2       ├─────┤ X ├
                    └────────┘               └────────┘     └───┘

We see how the qubits that will later hold qv are used to efficiently compile the mcx gate.

In situations of no free clean ancilla qubits, the Qrisp compiler even makes use of dirty ancillae. To demonstrate, we again create three QuantumVariables but this time we execute a cx-gate before executing the mcx-gate. This way qv has to be allocated before the mcx gate.

>>> ctrl = QuantumVariable(4)
>>> target = QuantumVariable(1)
>>> qv = QuantumVariable(2)
>>> cx(target[0], qv)
>>> mcx(ctrl, target)
>>> print(ctrl.qs.compile())
  ctrl.0: ────────────────────────────────────■──────────────────────────»
                         ┌─────────────────┐  │  ┌─────────────────┐     »
  ctrl.1: ───────────────┤1                ├──┼──┤1                ├─────»
                         │                 │  │  │                 │     »
  ctrl.2: ───────────────┤2                ├──┼──┤2                ├─────»
                         │                 │  │  │                 │     »
  ctrl.3: ────────────■──┤                 ├──┼──┤                 ├──■──»
                    ┌─┴─┐│  reduced_maslov │  │  │  reduced_maslov │┌─┴─┐»
target.0: ──■────■──┤ X ├┤                 ├──┼──┤                 ├┤ X ├»
          ┌─┴─┐  │  └─┬─┘│                 │┌─┴─┐│                 │└─┬─┘»
    qv.0: ┤ X ├──┼────┼──┤0                ├┤ X ├┤0                ├──┼──»
          └───┘┌─┴─┐  │  │                 │└───┘│                 │  │  »
    qv.1: ─────┤ X ├──■──┤3                ├─────┤3                ├──■──»
               └───┘     └─────────────────┘     └─────────────────┘     »
«
«  ctrl.0: ─────────────────────■─────────────────────
«          ┌─────────────────┐  │  ┌─────────────────┐
«  ctrl.1: ┤1                ├──┼──┤1                ├
«          │                 │  │  │                 │
«  ctrl.2: ┤2                ├──┼──┤2                ├
«          │                 │  │  │                 │
«  ctrl.3: ┤                 ├──┼──┤                 ├
«          │  reduced_maslov │  │  │  reduced_maslov │
«target.0: ┤                 ├──┼──┤                 ├
«          │                 │┌─┴─┐│                 │
«    qv.0: ┤0                ├┤ X ├┤0                ├
«          │                 │└───┘│                 │
«    qv.1: ┤3                ├─────┤3                ├
«          └─────────────────┘     └─────────────────┘

We see how the qubits of qv are utilized as dirty ancilla qubits in order to facilitate a more efficient mcx implementation compared to no ancillae at all.

Fully automized uncomputation

This feature is as of right now experimental. To demonstrate, we create a test function, creating a local QuantumBool

from qrisp import QuantumBool, mcx

def triple_AND(a, b, c):

    local = QuantumBool()
    result = QuantumBool()

    mcx([a,b], local)

    mcx([c, local], result)

    return result
>>> a = QuantumBool()
>>> b = QuantumBool()
>>> c = QuantumBool()
>>> res = triple_AND(a,b,c)
>>> print(res.qs)
QuantumCircuit:
--------------
     a.0: ──■───────
            │
     b.0: ──■───────
            │
     c.0: ──┼────■──
          ┌─┴─┐  │
 local.0: ┤ X ├──■──
          └───┘┌─┴─┐
result.0: ─────┤ X ├
               └───┘
Live QuantumVariables:
---------------------
QuantumBool a
QuantumBool b
QuantumBool c
QuantumBool local
QuantumBool result

We now compile with the corresponding keyword argument:

>>> print(a.qs.compile(disable_uncomputation = False))
             ┌────────┐     ┌────────┐
        a.0: ┤0       ├─────┤0       ├
             │        │     │        │
        b.0: ┤1       ├─────┤1       ├
             │        │     │        │
        c.0: ┤  pt2cx ├──■──┤  pt2cx ├
             │        │┌─┴─┐│        │
   result.0: ┤        ├┤ X ├┤        ├
             │        │└─┬─┘│        │
workspace_0: ┤2       ├──■──┤2       ├
             └────────┘     └────────┘

We see that the local QuantumBool is no longer allocated but has been uncomputed and it’s qubits are available as workspace.