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 withmethod = 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 theworkspace
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 thecompilation_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 isTrue
.- 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 isTrue
.
- 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 themcx
-gate. This wayqv
has to be allocated before themcx
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 efficientmcx
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.