"""
\********************************************************************************
* Copyright (c) 2023 the Qrisp authors
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License, v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is
* available at https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
"""
from qrisp.environments import QuantumEnvironment, control
from qrisp.environments.custom_control_environment import custom_control
from qrisp.circuit import Operation, ControlledOperation
from qrisp.core.session_merging_tools import recursive_qs_search, merge
from qrisp.misc import get_depth_dic
[docs]
class ConjugationEnvironment(QuantumEnvironment):
r"""
This :ref:`QuantumEnvironment<QuantumEnvironment>` can be used for perfoming conjugated operations.
An arbitrary unitary :math:`U \in SU(2^n)` can be conjugated by another unitary
:math:`V \in SU(2^n)`:
.. math::
\text{conj}(U,V) = V^\dagger U V
This structure appears in many quantum algorithms such as `Grover <https://arxiv.org/abs/quant-ph/9605043>`_,
`Quantum backtracking <https://arxiv.org/abs/1509.02374>`_ or
`Fourier arithmetic <https://arxiv.org/abs/quant-ph/0008033>`_.
Using the ``ConjugationEnvironment`` not only helps to structure the code,
but can also grant performance advantages.
This is because the controlled circuit of such a conjugation is can be
realized by just controlling :math:`U` instead of all three operations.
.. math::
C\text{conj}(U,V) = V^\dagger CU V
The ``ConjugationEnvironment`` can be called using the alias ``conjugate``.
Conjugate takes the conjugation function (in our example :math:`V`) and returns
a function that takes the arguments for the conjugation function and returns
the corresponding ``ConjugationEnvironment``.
For more information consult the examples section.
.. note::
Note that every QuantumVariable that is created by the conjugation
function :math:`V` must be deleted/uncomputed before function conclusion.
Parameters
----------
conjugation_function : function
The function performing the operation :math:`V`.
args : iterable
The arguments for the conjugation function.
kwargs : dict
The keyword arguments for the conjugation function.
Examples
--------
We perform Fourier addition on a :ref:`QuantumFloat`
::
from qrisp import conjugate, QuantumFloat, p, QFT
def fourier_adder(qf, n):
with conjugate(QFT)(qf):
for i in range(qf.size):
p(n*np.pi*2**(i-qf.size+1), qf[i])
>>> qf = QuantumFloat(5)
>>> fourier_adder(qf, 3)
>>> print(qf)
{3: 1.0}
>>> fourier_adder(qf, 2)
{5: 1.0}
Investigate the effects of a controlled addition:
::
from qrisp import control
ctrl = QuantumFloat(1)
qf = QuantumFloat(5)
with control(ctrl):
fourier_adder(qf, 3)
To see that indeed only the conjugand has been controlled we take a look
at the circuit:
>>> print(qf.qs.transpile(1))
::
ctrl.0: ─────────■──────────■─────────■─────────■─────────■─────────────────
┌──────┐ │P(3π/16) │ │ │ │ ┌─────────┐
qf.0: ┤0 ├─■──────────┼─────────┼─────────┼─────────┼──────┤0 ├
│ │ │P(3π/8) │ │ │ │ │
qf.1: ┤1 ├────────────■─────────┼─────────┼─────────┼──────┤1 ├
│ │ │P(3π/4) │ │ │ │
qf.2: ┤2 QFT ├──────────────────────■─────────┼─────────┼──────┤2 QFT_dg ├
│ │ │P(3π/2) │ │ │
qf.3: ┤3 ├────────────────────────────────■─────────┼──────┤3 ├
│ │ │P(3π) │ │
qf.4: ┤4 ├──────────────────────────────────────────■──────┤4 ├
└──────┘ └─────────┘
"""
def __init__(self, conjugation_function, args, kwargs, allocation_management = True):
self.conjugation_function = conjugation_function
self.args = args
self.kwargs = kwargs
self.manual_allocation_management = allocation_management
QuantumEnvironment.__init__(self)
def __enter__(self):
# merge(recursive_qs_search(self.args) + [self.env_qs])
QuantumEnvironment.__enter__(self)
merge(recursive_qs_search(self.args) + [self.env_qs])
qv_set_before = set(self.env_qs.qv_list)
res = self.conjugation_function(*self.args, **self.kwargs)
temp_data = list(self.env_qs.data)
self.env_qs.data = []
i = 0
while temp_data:
instr = temp_data.pop(i)
if isinstance(instr, QuantumEnvironment):
instr.compile()
else:
self.env_qs.append(instr)
if qv_set_before != set(self.env_qs.qv_list):
raise Exception(f"Tried to create/destroy QuantumVariables {qv_set_before.symmetric_difference(set(self.env_qs.qv_list))} within a conjugation")
self.conjugation_circ = self.env_qs.copy()
self.env_qs.data = []
return res
def __exit__(self, exception_type, exception_value, traceback):
conjugation_center_data = list(self.env_qs.data)
self.env_qs.data = []
self.perform_conjugation(conjugation_center_data)
QuantumEnvironment.__exit__(self, exception_type, exception_value, traceback)
@custom_control
def perform_conjugation(self, conjugation_center_data, ctrl = None, ctrl_method = None):
for instr in self.conjugation_circ.data:
self.env_qs.append(instr)
if ctrl is not None:
with control(ctrl, ctrl_method = ctrl_method):
self.env_qs.data.extend(conjugation_center_data)
else:
self.env_qs.data.extend(conjugation_center_data)
for instr in self.conjugation_circ.inverse().data:
self.env_qs.append(instr)
def compile_(self, ctrl = None):
temp = list(self.env_qs.data)
self.env_qs.data = []
for instr in self.conjugation_circ.data:
if isinstance(instr, QuantumEnvironment):
instr.compile()
else:
self.env_qs.append(instr)
self.conjugation_circ = self.env_qs.copy()
self.env_qs.data = []
QuantumEnvironment.compile(self)
content_circ = self.env_qs.copy()
self.conjugation_circ.qubits = list(content_circ.qubits)
conjugation_depth_dic = get_depth_dic(self.conjugation_circ)
content_depth_dic = get_depth_dic(content_circ)
added_depth_dic = {qb : conjugation_depth_dic[qb] + content_depth_dic[qb] for qb in content_circ.qubits}
instruction_qubits = []
i = 0
while i < len(content_circ.qubits):
qb = content_circ.qubits[i]
if added_depth_dic[qb]:
instruction_qubits.append(qb)
i += 1
else:
content_circ.qubits.pop(i)
self.conjugation_circ.qubits.pop(i)
self.env_qs.data = temp
conj_op = ConjugatedOperation(self.conjugation_circ, content_circ)
alloc_instr = [instr for instr in self.conjugation_circ.data + content_circ.data if instr.op.name == "qb_alloc"]
for instr in alloc_instr:
self.env_qs.append(instr)
self.env_qs.append(conj_op, content_circ.qubits)
dealloc_instr = [instr for instr in self.conjugation_circ.data + content_circ.data if instr.op.name == "qb_dealloc"]
for instr in dealloc_instr:
self.env_qs.append(instr)
class ConjugatedOperation(Operation):
def __init__(self, conjugation_circ, content_circ):
self.conjugation_gate = conjugation_circ.to_gate(name = "conjugator")
self.content_gate = content_circ.to_gate(name = "conjugand")
definition = conjugation_circ.clearcopy()
definition.append(self.conjugation_gate, definition.qubits)
definition.append(self.content_gate, definition.qubits)
definition.append(self.conjugation_gate.inverse(), definition.qubits)
Operation.__init__(self,
name = "conjugation_env",
definition = definition,
num_qubits = definition.num_qubits())
def control(self, num_ctrl_qubits=1, ctrl_state=-1, method=None):
controlled_conjugand = self.content_gate.control(num_ctrl_qubits=num_ctrl_qubits, ctrl_state=ctrl_state, method=None)
res = type(controlled_conjugand)(self, num_ctrl_qubits=num_ctrl_qubits, ctrl_state=ctrl_state, method=method)
res.definition.data = []
res.definition.append(self.conjugation_gate, self.definition.qubits)
res.definition.append(controlled_conjugand, res.definition.qubits[:num_ctrl_qubits] + self.definition.qubits)
res.definition.append(self.conjugation_gate.inverse(), self.definition.qubits)
return res
def inverse(self):
return ConjugatedOperation(self.conjugation_gate.definition, self.content_gate.inverse().definition)
def conjugate(conjugation_function, allocation_management = True):
def conjugation_env_creator(*args, **kwargs):
return ConjugationEnvironment(conjugation_function, args, kwargs, allocation_management = allocation_management)
return conjugation_env_creator