Experiment
An Experiment describes the elements of an optical table and the post-processing rules.
It can be created either using a circuit, a number of modes, or nothing, in which case the size will be determined at the first component addition.
>>> import perceval as pcvl
>>> e = pcvl.Experiment(2, name="my experiment").add(0, pcvl.BS())
Experiment composition
Components, circuits and other experiments can be added to experiments using the add() method
(note however that // doesn’t work for experiments).
>>> e.add(0, pcvl.PS(3.14)) # Add a phase shifter on mode 0
However, unlike Circuit, non-linear components can also be added to Experiments.
>>> e.add(1, pcvl.TD(1)) # Adds a time-delay on mode 1
Secondly, the mode on which a component is added has a few more options than just an integer. One can use a list or a dict of integers to map the output of the current experiment to the input of the added component. If the left experiment has output ports and the right experiment has input ports, it can also be a dict describing the port names.
This adds up a permutation before inserting the new component, and its inverse at the end (so modes don’t move when doing this).
Note however that when adding an experiment with asymmetrical heralds (see below),
the inverse permutation is not added since it doesn’t exist, so modes might move (check with a pdisplay).
>>> e.add([1, 0], pcvl.BS(theta=0.7)) # Left mode 1 will connect to right mode 0, and left mode 0 will connect to right mode 1
>>> e.add({1: 0, 0: 1}, pcvl.BS(theta=0.7)) # Same as above
Composition is a powerful tool to achieve complex experiments:
An experiment composed of a Hadamard gate and two heralded CNOT gates.
Detectors can also be added to an Experiment using the same syntax
>>> e.add(0, pcvl.Detector.threshold())
Once a Detector has been added, no optical component can be added anymore on this mode.
Setting an input state
Before an Experiment can be simulated, an input state must be provided.
>>> e.with_input(pcvl.BasicState([1, 0]))
The input state can be:
A
BasicState, in which case the noise from the noise model is computed.A
LogicalStateif ports have been defined, in which case the noise is computed.A
StateVectorA
SVDistribution
Min photons filter
A threshold on the number of detected photons can be set so outputs having less than this number of photons are filtered out. This has an impact on the perfs of the Experiment when computed with a Computer.
>>> e.min_detected_photons_filter(3) # Outputs will all have at least 3 photons
Ports
Once an Experiment has been defined in terms of components, one can add ports and heralds to it. If a port spans over several modes, the specified mode is considered to be the upper one.
>>> e.add_port(0, pcvl.Port(pcvl.Encoding.DUAL_RAIL, "qubit0")) # Adds a dual rail port on modes 0 and 1 on both sides
>>> e.remove_port(0)
>>> e.add_port(0, pcvl.Port(pcvl.Encoding.DUAL_RAIL, "qubit0"), location=pcvl.PortLocation.INPUT) # Add the port on the left of the experiment
Ports have three main purposes:
Showing the circuit’s logic in display
Composing experiments using ports
Setting an input state
>>> e.with_input(pcvl.LogicalState([0])) # Equivalent to BasicState([1, 0]) for a dual rail. Adapts automatically to the ports
Heralds
Heralds are a special kind of ports that act as modes that the user “doesn’t want to see”.
Note that ports and heralds are mutually exclusive mode-wise.
At the input, they declare a number of photon in a mode that the user won’t have to specify when using with_input.
>>> e = pcvl.Experiment(pcvl.BS())
>>> e.add_herald(0, 1, location=pcvl.PortLocation.INPUT) # Add an herald of value 1 on input mode 0
>>> e.with_input(pcvl.BasicState([1])) # Only one mode
>>> e.m_in
1
>>> e.heralds_in
{0: 1}
The input heralds can be removed from a state using state = remove_in_heralded_modes(state).
At the output, they will automatically filter states so only states matching the given number of photons will be selected. They also remove these modes from the resulting BasicStates. This filtering has an impact on the perf of the experiment.
>>> e = pcvl.Experiment("SLOS", pcvl.BS())
>>> e.add_herald(0, 1, location=pcvl.PortLocation.OUTPUT) # Output will have only one mode
>>> e.m
1
>>> e.circuit_size # Real size of the circuit
2
>>> e.heralds
{0: 1}
Heralded output modes can still be seen when simulating using a Simulator using simulator.keep_heralds(True).
In this case, heralded modes can still be removed afterward using state = e.remove_heralded_modes(state)
Heralds at output are independent from the min detected photons filter, as the filter looks only at non-heralded modes.
>>> e.min_detected_photons_filter(2)
>>> e.add_herald(0, 1) # There will actually be at least 3 photons
An Experiment that has at least one mode that defines an herald only at input or output is considered asymmetrical.
By default, heralds are added on both sides, so Experiments are kept symmetrical.
When composing experiments, the experiments are considered to have m output modes and m_in input modes.
Heralds are considered to be outside the experiments. Thus, they can be moved to new modes to keep a good structure.
Most 2-qubit gates from the catalog are symmetrical experiments that use heralds.
When composing with a symmetrical experiment, the inverse permutation is added at the right to keep the order of the modes. This is not the case when composing with an asymmetric experiment.
>>> from perceval import catalog
>>> e = pcvl.Experiment("SLOS", 4)
>>> cnot = catalog["postprocessed cnot"].build_experiment()
>>> cnot.m
4
>>> cnot.circuit_size
6
>>> e.add(0, cnot) # Works despite the cnot having 6 modes
>>> e.circuit_size # e is now bigger due to the added heralds from cnot
6
>>> e.heralds
{4: 0, 5: 0}
PostSelect
A post-selection method can be added to an Experiment to filter only states matching it.
>>> e.set_postselection(pcvl.PostSelect("[0, 1] == 2"))
>>> e.post_select_fn
[0, 1] == 2
When composing, the modes are swapped to match the new modes of the composition. Also, it is not allowed to add something to an experiment that has a post-selection if the modes overlap one of the nodes of the post-selection (they should be entirely included or disjoint)
If the user knows what they are doing,
they can remove the post-selection using e.clear_postselection() then apply it again.
- class perceval.components.experiment.Experiment(m_circuit=None, noise=None, name='Experiment')
This class represents an optical table containing:
A circuit and/or components that represent the operations that will operate on photons. Can contain non-unitary components
The input state for the experiment.
Detectors to detect photons.
Ports to define groups of modes
Heralds
A post-selection method
A NoiseModel (deprecated, should now be set into the computer)
- Parameters:
m_circuit (
Union[int,ACircuit,None]) – Number of spatial modes (int), first part of the circuit (Circuit) or None. If a circuit is passed, its size is used as the experiment size.noise (
Optional[NoiseModel]) – (deprecated) A NoiseModelname (
str) – The experiment name
- add(mode_mapping, component, keep_port=True)
Add a component to the experiment (unitary or non-unitary).
- Parameters:
mode_mapping –
Describe how the new component is connected to the existing experiment. Can be:
an int: composition uses consecutive modes starting from mode_mapping
a list or a dict: describes the full mapping of length the input mode count of component
component –
The component to append to the experiment. Can be:
A unitary circuit
A non-unitary component
A processor
An experiment
A detector
keep_port (
bool) – if True, saves self’s output ports on modes impacted by the new component, otherwise removes them.
Adding a component on non-ordered, non-consecutive modes computes the right permutation (PERM component) which fits into the existing experiment and the new component.
Example:
>>> e = Experiment(6) >>> e.add(0, BS()) # Modes (0, 1) connected to (0, 1) of the added beam splitter >>> e.add([2,5], BS()) # Modes (2, 5) of the experiment's output connected to (0, 1) of the added beam splitter >>> e.add({2:0, 5:1}, BS()) # Same as above
If the added component is a processor or an experiment with modes having heralds only on one side, no permutation will be added at the end, and the “in-between” modes will be pushed to the bottom.
- add_herald(mode, expected, name=None, location=PortLocation.IN_OUT)
Add a heralded mode
- Parameters:
mode (
int) – Mode index of the heraldexpected (
int) – number of expected photon as input AND output on the given mode (must be 0 or 1)name (
Optional[str]) – Herald port name. If none is passed, the name is auto-generatedlocation (
PortLocation) – Port location of the herald (input, output or both)
- apply_phase_noise(phase_error=0, phase_imprecision=0, rng=None)
Applies the given noise parameters to each PS component or affiliated following the given rules:
If a PS has max_error, this error is applied to the phase value, then removed
Else, if the given noise has phase_error, this error is applied to the phase value
Finally, if the noise has phase_imprecision, it is applied to the phase value
- are_modes_free(mode_range, location=PortLocation.OUTPUT)
- Return type:
bool- Returns:
True if all modes in mode_range are free of ports, for a given location (input, output or both)
- check_input(input_state)
Check if a basic state input matches with the current experiment configuration
- property circuit_size: int
- Returns:
Total size of the enclosed circuit (i.e. self.m + ancillary mode count)
- copy()
Performs a deep copy of the current experiment. :rtype:
Experiment:return: A copy of this experiment.
- property detectors
- Returns:
The list of detectors which were defined in the experiment.
- flatten(max_depth=None)
List all the components in the experiment where recursive circuits have been flattened.
- Parameters:
max_depth – The maximum depth of recursion. The remaining sub-circuits at this depth are listed as a component.
- Return type:
list[tuple]
- property has_feedforward: bool
- Returns:
True if the circuit contains at least one feed-forward layer, False otherwise.
- property has_td: bool
- Returns:
True if the circuit contains at least one time delay, False otherwise.
- property in_port_names
- Returns:
A list of the input port names. Names are repeated for ports connected to more than one mode
- property is_unitary: bool
- Returns:
True if the circuit is composed of only unitary components, False otherwise.
- property m: int
- Returns:
Number of modes of interest (MOI) at the output of the experiment
- property m_in
- Returns:
Number of modes of interest (MOI) at the input the experiment
- min_detected_photons_filter(n)
Sets-up a state post-selection on the number of detected photons. With thresholded detectors, this will actually filter on “click” count.
- Parameters:
n (
int) – Minimum expected photons
This post-selection has an impact on the output physical performance
- property out_port_names
- Returns:
A list of the output port names. Names are repeated for ports connected to more than one mode
- remove_all_ports(location=PortLocation.IN_OUT)
Remove all ports (including heralds) defined in this Experiment
- set_circuit(circuit)
Removes all components and replace them by the given circuit.
- Parameters:
circuit (
ACircuit) – The circuit to start the experiment with- Returns:
Self to allow direct chain this with .add()
- set_postselection(postselect)
Set a logical post-selection function. Along with the heralded modes, this function has an impact on the logical performance of the results when computing using this experiment
- Parameters:
postselect (
PostSelect|str) – Sets a post-selection function.
- unitary_circuit(flatten=False, use_phase_noise=False)
Creates a unitary circuit from internal components, if all internal components are unitary.
- Parameters:
flatten (
bool) – if True, the component recursive hierarchy is discarded, making the output circuit “flat”.- Return type:
- use_phase_noise(noise=None, seed=None)
Makes a copy of the current Experiment where the noise is applied to each PS component following the given rules:
If a PS has max_error, this error is applied to the phase value, then removed
Else, if the given noise has phase_error, this error is applied to the phase value
Finally, if the noise has phase_imprecision, it is applied to the phase value
- Parameters:
noise (
Optional[NoiseModel]) – A NoiseModel to use.seed (
Optional[int]) – Seed for the random number generator
- Return type:
- Returns:
A copy of self where the phase noise has been applied. No copy is made if there is no noise