Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pcvl 791 add symmetric mzi and improve generic interferometer #462

4 changes: 2 additions & 2 deletions docs/source/reference/logging.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ To log a message, you can use it the same way as the python logger:

.. code-block:: python

from perceval.utils import logger
from perceval.utils import get_logger
get_logger().info('I log something as info')
# or
logger = get_logger()
Expand Down Expand Up @@ -166,7 +166,7 @@ Example

.. code-block:: python

from perceval.utils.logging import get_logger(), channel, level
from perceval.utils.logging import get_logger, channel, level
logger = get_logger()
logger.enable_file()
logger.set_level(level.info, channel.resources)
Expand Down
6 changes: 3 additions & 3 deletions perceval/components/core_catalog/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@
from .heralded_cnot import HeraldedCnotItem
from .heralded_cz import HeraldedCzItem
from .generic_2mode import Generic2ModeItem
from .mzi import MZIPhaseFirst, MZIPhaseLast
from .mzi import MZIPhaseFirst, MZIPhaseLast, SymmetricMZI
from .postprocessed_ccz import PostProcessedCCZItem
from .toffoli import ToffoliItem
from .controlled_rotation_gates import PostProcessedControledRotationsItem
from .controlled_rotation_gates import PostProcessedControlledRotationsItem

catalog_items = [KLMCnotItem, HeraldedCnotItem, PostProcessedCnotItem, HeraldedCzItem, Generic2ModeItem, MZIPhaseFirst,
MZIPhaseLast, PostProcessedCCZItem, ToffoliItem, PostProcessedControledRotationsItem
MZIPhaseLast, SymmetricMZI, PostProcessedCCZItem, ToffoliItem, PostProcessedControlledRotationsItem
]
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def build_control_gate_unitary(n: int, alpha: float) -> Matrix:
return U


class PostProcessedControledRotationsItem(CatalogItem):
class PostProcessedControlledRotationsItem(CatalogItem):
article_ref = "https://arxiv.org/abs/2405.01395"
description = r"""n-qubit controlled rotation gate C...CZ(alpha) with 2*n ancillary modes and a post-selection function"""
params_doc = {
Expand Down
19 changes: 19 additions & 0 deletions perceval/components/core_catalog/mzi.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ def generate(self, i: int):

_NAME_MZI_PHASE_FIRST = "mzi phase first"
_NAME_MZI_PHASE_LAST = "mzi phase last"
_NAME_SYMMETRIC_MZI = "symmetric mzi"


class MZIPhaseFirst(AMZI):
Expand Down Expand Up @@ -97,3 +98,21 @@ def build_circuit(self, **kwargs) -> Circuit:
phi_a, phi_b, theta_a, theta_b = self._handle_params(**kwargs)
return (Circuit(2, name="MZI")
// BS(theta=theta_a) // (1, PS(phi=phi_a)) // BS(theta=theta_b) // (1, PS(phi=phi_b)))


class SymmetricMZI(AMZI):
str_repr = r""" ╭─────╮╭─────╮╭─────╮
0:──┤BS.Rx├┤phi_a├┤BS.Rx├──:0
│ │╰─────╯│ │
│ │╭─────╮│ │
1:──┤ ├┤phi_b├┤ ├──:1
╰─────╯╰─────╯╰─────╯ """
see_also = _NAME_MZI_PHASE_FIRST

def __init__(self):
super().__init__(_NAME_SYMMETRIC_MZI)

def build_circuit(self, **kwargs) -> Circuit:
phi_a, phi_b, theta_a, theta_b = self._handle_params(**kwargs)
return (Circuit(2, name="MZI")
ericbrts marked this conversation as resolved.
Show resolved Hide resolved
// BS(theta=theta_a) // (0, PS(phi=phi_a)) // (1, PS(phi=phi_b))) // BS(theta=theta_b)
48 changes: 45 additions & 3 deletions perceval/components/generic_interferometer.py
ericbrts marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

from .linear_circuit import ACircuit, Circuit


from perceval.utils import InterferometerShape
from perceval.utils.logging import get_logger, channel

Expand All @@ -42,13 +43,19 @@ class GenericInterferometer(Circuit):
:param m: number of modes
:param fun_gen: generator function for the building components, index is an integer allowing to generate
named parameters - for instance:
:code:`fun_gen=lambda idx: phys.BS()//(0, phys.PS(pcvl.P(f"phi_{idx}")))`
:code:`fun_gen=lambda idx: pcvl.BS()//(0, pcvl.PS(pcvl.P(f"phi_{idx}")))`
:param shape: The output interferometer shape (InterferometerShape.RECTANGLE or InterferometerShape.TRIANGLE)
:param depth: if None, maximal depth is :math:`m-1` for rectangular shape, :math:`m` for triangular shape.
Can be used with :math:`2*m` to reproduce :cite:`fldzhyan2020optimal`.
:param phase_shifter_fun_gen: a function generating a phase_shifter circuit.
:param phase_at_output: if True creates a layer of phase shifters at the output of the generated interferometer
else creates it in the input (default: False)
:param upper_component_gen fun_gen: generator function for the building the upper component, index is an integer allowing to generate
named parameters - for instance:
:code:`fun_gen=lambda idx: pcvl.PS(pcvl.P(f"phi_upper_{idx}"))`
:param lower_component_gen: generator function for the building the lower component, index is an integer allowing to generate
named parameters - for instance:
:code:`fun_gen=lambda idx: pcvl.PS(pcvl.P(f"phi_lower_{idx}"))`

See :cite:`fldzhyan2020optimal`, :cite:`clements2016optimal` and :cite:`reck1994experimental`
"""
Expand All @@ -58,7 +65,9 @@ def __init__(self,
shape: InterferometerShape = InterferometerShape.RECTANGLE,
depth: int = None,
phase_shifter_fun_gen: Optional[Callable[[int], ACircuit]] = None,
phase_at_output: bool = False):
phase_at_output: bool = False,
upper_component_gen: Callable[[int], ACircuit] = None,
lower_component_gen: Callable[[int], ACircuit] = None):
assert isinstance(shape, InterferometerShape),\
f"Wrong type for shape, expected InterferometerShape, got {type(shape)}"
super().__init__(m)
Expand All @@ -67,6 +76,8 @@ def __init__(self,
self._depth_per_mode = [0] * m
self._pattern_generator = fun_gen
self._has_input_phase_layer = False
self._upper_component_gen = upper_component_gen
self._lower_component_gen = lower_component_gen
if phase_shifter_fun_gen and not phase_at_output:
self._has_input_phase_layer = True
for i in range(0, m):
Expand All @@ -75,6 +86,8 @@ def __init__(self,
if shape == InterferometerShape.RECTANGLE:
self._build_rectangle()
elif shape == InterferometerShape.TRIANGLE:
if upper_component_gen or lower_component_gen:
get_logger().warn(f"upper_component_gen or lower_component_gen cannot be applied for shape {shape}")
self._build_triangle()
else:
raise NotImplementedError(f"Shape {shape} not supported")
Expand Down Expand Up @@ -106,15 +119,44 @@ def set_identity_mode(self):
for p in self.get_parameters():
p.set_value(math.pi)

def _add_single_mode_component(self, mode: int, component: ACircuit) -> None:
"""Add a component to the circuit, check if it's a one mode circuit

:param mode: mode to add the component
:param component: component to add
"""
assert component.m == 1, f"Component should always be a one mode circuit, instead it's a {component.m} modes circuit"
self.add(mode, component)

def _add_upper_component(self, i_depth: int) -> None:
"""Add a component with upper_component_gen between the interferometer on the first mode

:param i_depth: depth index of the interferometer
"""
if self._upper_component_gen and i_depth % 2 == 1:
self._add_single_mode_component(0, self._upper_component_gen(i_depth // 2))

def _add_lower_component(self, i_depth: int) -> None:
"""Add a component with lower_component_gen between the interferometer on the last mode

:param i_depth: depth index of the interferometer
"""
# If m is even, the component is added at even depth index, else it's added in at odd depth index
if (self._lower_component_gen and
((i_depth % 2 == 1 and self.m % 2 == 0) or (i_depth % 2 == 0 and self.m % 2 == 1))):
self._add_single_mode_component(self.m - 1, self._lower_component_gen(i_depth // 2))

def _build_rectangle(self):
max_depth = self.m if self._depth is None else self._depth
idx = 0
for i in range(0, max_depth):
self._add_upper_component(i)
self._add_lower_component(i)
for j in range(0+i%2, self.m-1, 2):
if self._depth is not None and (self._depth_per_mode[j] == self._depth
or self._depth_per_mode[j+1] == self._depth):
continue
self.add((j, j+1), self._pattern_generator(idx), merge=True, x_grid=i)
self.add((j, j+1), self._pattern_generator(idx), merge=True)
self._depth_per_mode[j] += 1
self._depth_per_mode[j+1] += 1
idx += 1
Expand Down
2 changes: 1 addition & 1 deletion perceval/components/processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ def log_resources(self, method: str, extra_parameters: Dict):
elif isinstance(self._input_state, SVDistribution):
my_dict['n'] = self._input_state.n_max
else:
get_logger().info(f"Cannot get n for type {type(self._input_state)}", channel.resource)
get_logger().error(f"Cannot get n for type {type(self._input_state)}", channel.general)
if extra_parameters:
my_dict.update(extra_parameters)
if self.noise: # TODO: PCVL-782
Expand Down
Loading