merlin.pcvl_pytorch.locirc_to_tensor module

class merlin.pcvl_pytorch.locirc_to_tensor.AComponent(m, name=None)

Bases: ABC

DEFAULT_NAME = None
is_composite()

Returns True if the component is itself composed of subcomponents

Return type:

bool

property m: int
property name: str

Returns component name

class merlin.pcvl_pytorch.locirc_to_tensor.BS(theta=pi / 2, phi_tl=0, phi_bl=0, phi_tr=0, phi_br=0, convention=BSConvention.Rx)

Bases: ACircuit

Beam splitter

DEFAULT_NAME = 'BS'
static H(theta=pi / 2, phi_tl=0, phi_bl=0, phi_tr=0, phi_br=0)
static Rx(theta=pi / 2, phi_tl=0, phi_bl=0, phi_tr=0, phi_br=0)
static Ry(theta=pi / 2, phi_tl=0, phi_bl=0, phi_tr=0, phi_br=0)
property convention
definition()
describe()
get_variables()
inverse(v=False, h=False)
property name

Returns component name

static r_to_theta(r)

Compute theta given a reflectivity value

Parameters:

r (float | Parameter) – reflectivity value (can be variable)

Return type:

float | Parameter

property reflectivity
static theta_to_r(theta)

Compute reflectivity given a theta value

Parameters:

theta (float | Parameter) – theta angle (can be variable)

Return type:

float | Parameter

enum merlin.pcvl_pytorch.locirc_to_tensor.BSConvention(value)

Bases: Enum

Valid values are as follows:

Rx = <BSConvention.Rx: 0>
Ry = <BSConvention.Ry: 1>
H = <BSConvention.H: 2>
class merlin.pcvl_pytorch.locirc_to_tensor.Barrier(m, visible=True)

Bases: ACircuit

Behaves like an identity unitary, visually represented as a barrier.

DEFAULT_NAME = 'I'
apply(r, sv)
definition()
describe()
inverse(v=False, h=False)
property visible
class merlin.pcvl_pytorch.locirc_to_tensor.Circuit(m, name=None)

Bases: ACircuit

Class to represent any circuit composed of one or multiple components

Parameters:
  • m (int) – The number of port of the circuit

  • name (str) – Name of the circuit

DEFAULT_NAME = 'CPLX'
add(port_range, component, merge=False)

Add a component in a circuit

Parameters:
  • port_range (int | tuple[int, ...]) – the port range as a tuple of consecutive ports, or the initial port where to add the component

  • component (ACircuit) – the component to add, must be a linear component or circuit

  • merge (bool) – when the component is a complex circuit, if True, flatten the added circuit. Otherwise, keep the nested structure (default False)

Return type:

Circuit

Returns:

the circuit itself, allowing to add multiple components in a same line

Raise:

AssertionError if parameters are not valid

barrier()

Add a barrier to a circuit

The barrier is a visual marker to break down a circuit into sections. Behind the scenes, it is implemented as a Barrier unitary operating on all modes.

At the moment, the barrier behaves exactly like a component with a unitary equal to identity.

compute_unitary(use_symbolic=False, assign=None, use_polarization=None)

Compute the unitary matrix corresponding to the circuit

Parameters:
  • use_symbolic (bool) – True to compute a symbolic matrix, False to compute a numerical matrix (default False)

  • assign (dict) – optional mapping between parameter names and their corresponding values

  • use_polarization (bool) – ask for polarized circuit to double size unitary matrix

Return type:

Matrix

Returns:

The circuit unitary matrix

copy(subs=None)

Return a deep copy of the current circuit

static decomposition(U, component, phase_shifter_fn=None, shape=InterferometerShape.TRIANGLE, permutation=None, inverse_v=False, inverse_h=False, constraints=None, merge=True, precision=1e-06, max_try=10, allow_error=False, ignore_identity_block=True)

Decompose a given unitary matrix U into a circuit with a specified component type

Parameters:
  • U (MatrixN) – the matrix to decompose

  • component (ACircuit) – a circuit, to solve any decomposition must have up to 2 independent parameters

  • phase_shifter_fn (Callable[[int], ACircuit]) – a function generating a phase_shifter circuit. If None, residual phase will be ignored

  • shape (str | InterferometerShape) – shape of the decomposition (triangle is natively supported in Perceval)

  • permutation (type[ACircuit]) – if provided, type of permutation operator to avoid unnecessary operators

  • inverse_v (bool) – inverse the decomposition vertically

  • inverse_h (bool) – inverse the decomposition horizontally

  • constraints – constraints to apply on both parameters, it is a list of individual constraints. Each constraint should have the numbers of free parameters of the system.

  • merge (bool) – don’t use sub-circuits

  • precision (float) – for intermediate values - norm below precision are considered 0. If not - use global_params

  • max_try (int) – number of times to try the decomposition

  • allow_error (bool) – allow decomposition error - when the actual solution is not locally reachable

  • ignore_identity_block (bool) – If true, do not insert a component when it’s not needed (component is an identity) Otherwise, insert a component everytime (default True).

Returns:

a circuit

definition()

Gives mathematical definition of the circuit

Only defined for elementary circuits

Return type:

Matrix

depths()

Return depth of the circuit for each mode

describe()

Describe a circuit

Return type:

str

Returns:

a string describing the circuit that be re-used to define the circuit

find_subnodes(pos)

find the subnodes of a given component (Udef for pos==None)

Parameters:

pos (int) – the position of the current node

Return type:

list[int]

Returns:

getitem(idx, only_parameterized=False)

Direct access to components of the circuit :type idx: tuple[int, int] :param idx: index of the component as (row, col) :type only_parameterized: bool :param only_parameterized: if True, only count components with parameters :rtype: ACircuit :return: the component

inverse(v=False, h=False)
is_composite()

Returns True if the component is itself composed of subcomponents

isolate(lc, name=None, color=None)
match(pattern, pos=None, pattern_pos=0, browse=False, match=None, actual_pos=None, actual_pattern_pos=None, reverse=False)

match a sub-circuit at a given position

Parameters:
  • match (Match) – the partial match

  • browse (bool) – true if we want to search the pattern at any location in the current circuit, if true, pos should be None

  • pattern (ACircuit) – the pattern to search for

  • pos (int) – the start position in the current circuit

  • pattern_pos (int) – the start position in the pattern

  • actual_pos (int) – unused, parameter only used by parent class

  • actual_pattern_pos (int) – unused, parameter only used by parent class

  • reverse (bool) – true if we want to search the pattern from the end of the circuit to pos (or the 0 if browse)

Return type:

Optional[Match]

Returns:

ncomponents()

Return number of actual components in the circuit

replace(p, pattern, merge=False)
property requires_polarization: bool

Does the circuit require polarization?

Returns:

is True if the circuit has a polarization component

transfer_from(source, force=False)

Transfer parameters of a circuit to the current one

Parameters:
  • source (ACircuit) – the circuit to transfer the parameters from. The shape of the circuit to transfer from should be a subset of the current circuit.

  • force (bool) – force changing fixed parameter if necessary

class merlin.pcvl_pytorch.locirc_to_tensor.CircuitConverter(circuit, input_specs=None, dtype=torch.complex64, device=device(type='cpu'))

Bases: object

Convert a parameterized Perceval circuit into a differentiable PyTorch unitary matrix.

This class converts Perceval quantum circuits into PyTorch tensors that can be used in neural network training with automatic differentiation. It supports batch processing for efficient training and handles various quantum components like beam splitters, phase shifters, and unitary operations.

Supported Components:
  • PS (Phase Shifter)

  • BS (Beam Splitter)

  • PERM (Permutation)

  • Unitary (Generic unitary matrix)

  • Barrier (no-op, removed during compilation)

Attributes:

circuit: The Perceval circuit to convert param_mapping: Maps parameter names to tensor indices device: PyTorch device for tensor operations tensor_cdtype: Complex tensor dtype tensor_fdtype: Float tensor dtype

Example:

Basic usage with a single phase shifter:

>>> import torch
>>> import perceval as pcvl
>>> from merlin.pcvl_pytorch.locirc_to_tensor import CircuitConverter
>>>
>>> # Create a simple circuit with one phase shifter
>>> circuit = pcvl.Circuit(1) // pcvl.PS(pcvl.P("phi"))
>>>
>>> # Convert to PyTorch with gradient tracking
>>> converter = CircuitConverter(circuit, input_specs=["phi"])
>>> phi_params = torch.tensor([0.5], requires_grad=True)
>>> unitary = converter.to_tensor(phi_params)
>>> print(unitary.shape)  # torch.Size([1, 1])

Multiple parameters with grouping:

>>> # Circuit with multiple phase shifters
>>> circuit = (pcvl.Circuit(2)
...            // pcvl.PS(pcvl.P("theta1"))
...            // (1, pcvl.PS(pcvl.P("theta2"))))
>>>
>>> converter = CircuitConverter(circuit, input_specs=["theta"])
>>> theta_params = torch.tensor([0.1, 0.2], requires_grad=True)
>>> unitary = converter.to_tensor(theta_params)
>>> print(unitary.shape)  # torch.Size([2, 2])

Batch processing for training:

>>> # Batch of parameter values
>>> batch_params = torch.tensor([[0.1], [0.2], [0.3]], requires_grad=True)
>>> converter = CircuitConverter(circuit, input_specs=["phi"])
>>> batch_unitary = converter.to_tensor(batch_params)
>>> print(batch_unitary.shape)  # torch.Size([3, 1, 1])

Training integration:

>>> # Training loop with beam splitter
>>> circuit = pcvl.Circuit(2) // pcvl.BS.Rx(pcvl.P("theta"))
>>> converter = CircuitConverter(circuit, ["theta"])
>>> theta = torch.tensor([0.5], requires_grad=True)
>>> optimizer = torch.optim.Adam([theta], lr=0.01)
>>>
>>> for step in range(10):
...     optimizer.zero_grad()
...     unitary = converter.to_tensor(theta)
...     loss = some_loss_function(unitary)
...     loss.backward()
...     optimizer.step()
set_dtype(dtype)

Set the tensor data types for float and complex operations.

Args:

dtype: Target dtype (float32/complex64 or float64/complex128)

Raises:

TypeError: If dtype is not supported

to(dtype, device)

Move the converter to a specific device and dtype.

Args:

dtype: Target tensor dtype (float32/complex64 or float64/complex128) device: Target device (string or torch.device)

Returns:

Self for method chaining

Raises:

TypeError: If device type or dtype is not supported

to_tensor(*input_params, batch_size=None)

Convert the parameterized circuit to a PyTorch unitary tensor.

Return type:

Tensor

Args:

*input_params: Variable number of parameter tensors. Each tensor has shape (num_params,) or (batch_size, num_params) corresponding to input_specs order. batch_size: Explicit batch size. If None, inferred from input tensors.

Returns:
Complex unitary tensor of shape (circuit.m, circuit.m) for single samples

or (batch_size, circuit.m, circuit.m) for batched inputs.

Raises:

ValueError: If wrong number of input tensors provided. TypeError: If input_params is not a list or tuple.

class merlin.pcvl_pytorch.locirc_to_tensor.PERM(perm)

Bases: Unitary

Permutation

DEFAULT_NAME = 'PERM'
apply(r, sv)
break_in_2_mode_perms()

Breaks any n-mode PERM into an equivalent circuit with only 2 mode PERMs

Returns:

An equivalent Circuit with only 2 mode PERM components

definition()
describe()
property perm_vector
class merlin.pcvl_pytorch.locirc_to_tensor.PS(phi, max_error=0)

Bases: ACircuit

Phase shifter

DEFAULT_NAME = 'PS'
describe()
get_variables()
inverse(v=False, h=False)
merlin.pcvl_pytorch.locirc_to_tensor.SUPPORTED_COMPONENTS = (<class 'perceval.components.unitary_components.PS'>, <class 'perceval.components.unitary_components.BS'>, <class 'perceval.components.unitary_components.PERM'>, <class 'perceval.components.unitary_components.Unitary'>, <class 'perceval.components.unitary_components.Barrier'>)

Tuple of quantum components supported by CircuitConverter.

Components:

PS: Phase shifter with single phi parameter BS: Beam splitter with theta and four phi parameters PERM: Mode permutation (no parameters) Unitary: Generic unitary matrix (no parameters) Barrier: Synchronization barrier (removed during compilation)

class merlin.pcvl_pytorch.locirc_to_tensor.Unitary(U, name=None, use_polarization=False)

Bases: ACircuit

Generic component defined by a unitary matrix

DEFAULT_NAME = 'Unitary'
describe()
inverse(v=False, h=False)
merlin.pcvl_pytorch.locirc_to_tensor.dispatch(*types, **kwargs)

Dispatch function on the types of the inputs

Supports dispatch on all non-keyword arguments.

Collects implementations based on the function name. Ignores namespaces.

If ambiguous type signatures occur a warning is raised when the function is defined suggesting the additional method to break the ambiguity.

Examples

>>> @dispatch(int)
... def f(x):
...     return x + 1
>>> @dispatch(float)
... def f(x):
...     return x - 1
>>> f(3)
4
>>> f(3.0)
2.0

Specify an isolated namespace with the namespace keyword argument

>>> my_namespace = dict()
>>> @dispatch(int, namespace=my_namespace)
... def foo(x):
...     return x + 1

Dispatch on instance methods within classes

>>> class MyClass(object):
...     @dispatch(list)
...     def __init__(self, data):
...         self.data = data
...     @dispatch(int)
...     def __init__(self, datum):
...         self.data = [datum]