Quantum Environments#
- class QuantumEnvironment[source]#
QuantumEnvironments are blocks of code, that undergo some user-specified compilation process. They can be entered using the
with
statementfrom qrisp import QuantumEnvironment, QuantumVariable, x qv = QuantumVariable(5) with QuantumEnvironment(): x(qv)
In this case we have no special compilation technique, since the abstract baseclass simply returns it’s content:
>>> print(qv.qs)
QuantumCircuit: -------------- ┌───┐ qv.0: ┤ X ├ ├───┤ qv.1: ┤ X ├ ├───┤ qv.2: ┤ X ├ ├───┤ qv.3: ┤ X ├ ├───┤ qv.4: ┤ X ├ └───┘ Live QuantumVariables: --------------------- QuantumVariable qv
More advanced environments allow for a large variety of features and can significantly simplify code development and maintainance.
The most important built-in QuantumEnvironments are:
Due to sophisticated condition evaluation of nested ConditionEnvironment and ControlEnvironment, using QuantumEnvironments even can bring an increase in performance, compared to the control method which is commonly implemented by QuantumCircuit-based approaches.
Uncomputation within QuantumEnvironments
Uncomputation via the
uncompute
method is possible only if the QuantumVariable has been created within the same or a sub-environment:from qrisp import QuantumVariable, QuantumEnvironment, cx a = QuantumVariable(1) with QuantumEnvironment(): b = QuantumVariable(1) cx(a,b) with QuantumEnvironment(): c = QuantumVariable(1) cx(b,c) c.uncompute() # works because c was created in a sub environment b.uncompute() # works because b was created in the same environment # a.uncompute() # doesn't work because a was created outside this environment.
>>> print(a.qs)
QuantumCircuit: -------------- a.0: ──■──────────────■── ┌─┴─┐ ┌─┴─┐ b.0: ┤ X ├──■────■──┤ X ├ └───┘┌─┴─┐┌─┴─┐└───┘ c.0: ─────┤ X ├┤ X ├───── └───┘└───┘ Live QuantumVariables: --------------------- QuantumVariable a
Visualisation within QuantumEnvironments
Calling
print
on a QuantumSession inside a QuantumEnvironment will display only the instructions, that have been performed within this environment.from qrisp import x, y, z a = QuantumVariable(3) x(a[0]) with QuantumEnvironment(): y(a[1]) with QuantumEnvironment(): z(a[2]) print(a.qs) print(a.qs) print(a.qs)
Executing this snippet yields
QuantumCircuit: -------------- a.0: ───── a.1: ───── ┌───┐ a.2: ┤ Z ├ └───┘ QuantumEnvironment Stack: ------------------------ Level 0: QuantumEnvironment Level 1: QuantumEnvironment Live QuantumVariables: --------------------- QuantumVariable a QuantumCircuit: -------------- a.0: ───── ┌───┐ a.1: ┤ Y ├ ├───┤ a.2: ┤ Z ├ └───┘ QuantumEnvironment Stack: ------------------------ Level 0: QuantumEnvironment Live QuantumVariables: --------------------- QuantumVariable a QuantumCircuit: -------------- ┌───┐ a.0: ┤ X ├ ├───┤ a.1: ┤ Y ├ ├───┤ a.2: ┤ Z ├ └───┘ Live QuantumVariables: --------------------- QuantumVariable a
Warning
Calling
print
within a QuantumEnvironment causes all sub environments to be compiled. While this doesn’t change the semantics of the resulting circuit, especially nested Condition- and ControlEnvironments lose a lot of efficiency if compiled prematurely. Therefore,print
-calls within QuantumEnvironments are usefull for debugging purposes but should be removed, if efficiency is a concern.Creating custom QuantumEnvironments
More interesting QuantumEnvironments can be created by inheriting and modifying the compile method. In the following code snippet, we will demonstrate how to set up a QuantumEnvironment, that skips every second instruction. We do this by inheriting from the QuantumEnvironment class. This will provide us with the necessary attributes for writing the compile method:
#.
.env_data
, which is the list of instructions, that have been appended in this environment. Note that child environments append themselves in this list upon exiting.#.
.env_qs
which is a QuantumSession, where all QuantumVariables, that operated inside this environment, are registered.The compile method is then called once all environments of
.env_qs
have been exited. Note that this doesn’t neccessarily imply that all QuantumEnvironments have been left. For more information about the interplay between QuantumSessions and QuantumEnvironments check the session merging documentation.class ExampleEnvironment(QuantumEnvironment): def compile(self): for i in range(len(self.env_data)): #This line makes sure every second instruction is skipped if i%2: continue instruction = self.env_data[i] #If the instruction is an environment, we compile this environment if isinstance(instruction, QuantumEnvironment): instruction.compile() #Otherwise we append else: self.env_qs.append(instruction)
Check the result:
from qrisp import x, y, z, t, s, h qv = QuantumVariable(6) with ExampleEnvironment(): x(qv[0]) y(qv[1]) with ExampleEnvironment(): z(qv[2]) t(qv[3]) with ExampleEnvironment(): s(qv[4]) h(qv[5])
>>> print(qv.qs)
QuantumCircuit: -------------- ┌───┐ qv.0: ┤ X ├ └───┘ qv.1: ───── ┌───┐ qv.2: ┤ Z ├ └───┘ qv.3: ───── qv.4: ───── ┌───┐ qv.5: ┤ H ├ └───┘ Live QuantumVariables: --------------------- QuantumVariable qv