Circuit
The basic usage and definition of Circuit can be found in the tutorial.
Accessing the components
Circuits can be iterated over to retrieve their components.
Each component is a tuple (r, c)
where r
is a tuple of integers corresponding to the modes of the component, in ascending order,
and c
is the component instance itself.
>>> import perceval as pcvl
>>> circuit = pcvl.Circuit(3) // pcvl.BS() // (1, pcvl.PS(1)) // (1, pcvl.BS())
>>> for r, c in circuit:
>>> print(r, c.name)
(0, 1) BS.Rx
(1,) PS
(1, 2) BS.Rx
Note
The iterator on a Circuit
flattens the circuit structure,
so only basic components will be returned when using a for
loop on a circuit.
It is also possible to access directly a component from a circuit using row and column indices - note that a same component may have different column indices for the different rows it spans over:
>>> c = Circuit(2) // comp.BS.H() // comp.PS(P("phi1")) // comp.BS.Rx() // comp.PS(P("phi2"))
>>> print(c[1, 1].describe())
BS(convention=BSConvention.Rx)
>>> print(c[0, 2].describe())
BS(convention=BSConvention.Rx)
Circuit and parameters
For circuits or components using symbolic parameters (see Parameter), some convenient ways to access them exist.
Note that two parameters in the same circuit can’t have the same name.
If a parameter’s name appears in more than one component, it can only be the same Parameter
instance.
Probably the most useful of them is assign()
,
as it allows setting the value for all variable parameters of a circuit at once,
even without having to store them somewhere.
>>> p = pcvl.P("phi0")
>>> c = pcvl.PS(p)
>>> c.assign({"phi0": 2.53})
>>> print(float(p))
2.53
Note
The assign
argument of compute_unitary()
does exactly that if you want to compute the unitary with values.
Beware however that this has the side-effect of changing the values of the parameters even outside compute_unitary()
.
You can remove the values for the variable parameters to get back sympy expressions using the circuit’s reset_parameters()
method.
The names of the parameters can be obtained using the params()
property.
Note that this includes the fixed parameters if there are any.
To get the Parameter
itself, there are three ways:
use the
param("param name")()
method to retrieve a single parameter from its name.use the
get_parameters()
method that gives all the parameters defined by the arguments (variable or all, with or without expressions). This is the preferred method for getting all parameters.>>> c = BS(theta=pcvl.P("alpha1")) // PS(pcvl.P("phi")) // BS(theta=pcvl.P("alpha2")) >>> for params in c.get_parameters(): >>> print(param) Parameter(name='alpha1', value=None, min_v=0.0, max_v=12.566370614359172) Parameter(name='phi', value=None, min_v=0.0, max_v=6.283185307179586) Parameter(name='alpha2', value=None, min_v=0.0, max_v=12.566370614359172)
use the
vars()
property to get a dictionary mapping the name of the variable parameters and their instances.
- class perceval.components.linear_circuit.Circuit(m, name=None)
Class to represent any circuit composed of one or multiple components
- Parameters:
m (
int
) – The number of port of the circuitname (
Optional
[str
]) – Name of the circuit
- property U
get the symbolic unitary matrix
- __floordiv__(component)
Build a new circuit by adding component to the current circuit
>>> c = a // b # equivalent to: `Circuit(n) // self // component`
- Parameters:
component (ACircuit | tuple[int, ACircuit]) – the component to add, or a tuple (first_port, component)
- Return type:
- __ifloordiv__(component)
Shortcut for
.add
>>> c //= b # equivalent to: `c.add((0:b.m),b)` >>> c //= (i, b) # equivalent to: `c.add((i:i+b.m), b)`
- Parameters:
component (ACircuit | tuple[int, ACircuit]) – the component to add, or a tuple (first_port, component)
- Return type:
- __imatmul__(component)
Add a barrier and a component to the current circuit
- Parameters:
component (ACircuit | tuple[int, ACircuit]) – the component to add, or a tuple (first_port, component)
- Return type:
- __iter__()
Iterator on a circuit, recursively returns modes and components in the order they were added to the circuit. Flattens the circuit if there are sub-circuits.
- Returns:
generator of tuples (r, c) where r is the tuple containing the modes of the component in ascending order, and c is the component itself.
- __matmul__(component)
Build a new circuit by adding a barrier and then component to the current circuit
- Parameters:
component (ACircuit | tuple[int, ACircuit]) – the component to add, or a tuple (first_port, component)
- Return type:
- 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:
- Returns:
the circuit itself, allowing to add multiple components in a same line
- Raise:
AssertionError
if parameters are not valid
- assign(assign=None)
Assign values to parameters referenced in assign
- Parameters:
assign (dict[str, float | int]) – A dictionary mapping parameter_name -> value. Set the value to the parameter whose name is parameter_name for each key of the dictionary.
- Raises:
KeyError – If parameter_name is not an existing variable parameter name of the circuit.
- 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 current circuit
- Parameters:
use_polarization (
Optional
[bool
]) – ask for polarized circuit to double size unitary matrixassign (
Optional
[dict
]) – optional mapping between parameter names and their corresponding valuesuse_symbolic (
bool
) – if the matrix should use symbolic calculation
- Return type:
- Returns:
the unitary matrix, will be a
MatrixS
if symbolic, or a ~`MatrixN` if not.
- 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
- property defined: bool
check if all parameters of the circuit are fully defined
- definition()
Gives mathematical definition of the circuit
Only defined for elementary circuits
- Return type:
- depths()
- Returns:
the 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:
- get_parameters(all_params=False, expressions=False)
Return the parameters of the circuit
- Parameters:
all_params (
bool
) – if False, only returns the variable parameters- Expressions:
if True, returns highest level Expressions and parameters only. If False, returns the raw parameters that make up the expressions only. Default False.
- Return type:
list
[Parameter
]- Returns:
the list of parameters
- 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
- identify(unitary_matrix, phases, precision=None, max_try=10, allow_error=False)
Identify an instance of the current circuit (should be parameterized) such as \(Q.C=U.P\) where \(Q\) and \(P\) are single-mode phase shifts (resp. \([q1, q2, ..., qn]\), and \([p1, p2, ...,pn]\)). This is solved through \(n^2\) equations: \(q_i * C_{i,j}(x,y, ...) = UP_{i,j} * p_j\)
- Parameters:
unitary_matrix – the matrix to identify
phases – phase shift parameters
max_try – the resolution is using parameter search starting on a random points, it might fail, this parameter sets the maximal number of times to try
- Return type:
None
- inverse(v=False, h=False)
Inverts a circuit in place, depending on the values of
h
andv
.- Parameters:
h – Stands for horizontal. If True, the circuit will be reversed such that its final unitary matrix is the inverse of the original one.
v – Stands for vertical. If True, the circuit will be reversed such that the mode 0 becomes the mode \(m-1\), the mode 1 becomes the mode \(m - 2\)…
- is_composite()
- Returns:
True if the component is itself composed of subcomponents
- 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:
Match | None
- Returns:
- property name: str
Returns component name
- ncomponents()
- Returns:
number of actual components in the circuit
- param(param_name)
Extract a Parameter object from its name :type param_name:
str
:param param_name: The name of the parameter :rtype:Parameter
:return: A Parameter object
- property params: Iterable[str]
- Returns:
a list of all variable parameter names in the component
- 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
- property vars: dict[str, perceval.utils.parameter.Parameter]
- Returns:
A dictionary mapping parameter names to parameters for all variable parameters of the circuit