Legacy

Perceval has evolved quickly from the initial release, some evolution are introducing breaking changes for existing code. While we are trying hard to avoid unnecessary API changes, we may break some in order to bring new features and keep a consistent code base.

This section lists the major breaking changes introduced.

Breaking changes in Perceval 0.12

Simulator.probs_svd method signature changed

The detectors parameter was introduced at 2nd position in the signature of Simulator.probs_svd, between the SVDistribution and the optional progress callback. If you were using such callbacks, please update your code from

>>> results = simulator.probs_svd(svd, my_callback)  # Would work pre-0.12

to either

>>> results = simulator.probs_svd(svd, progress_callback=my_callback)

or

>>> results = simulator.probs_svd(svd, my_detector_list, my_callback)  # my_detector_list can be None

PostSelect API changes

The PostSelect class, used to represent a set of post-selection conditions, was moved from Python to C++. This change allows supporting a richer boolean syntax, including nested condition based on more than the and operator.

Removal of operator methods

Now that we support nested logical expression, adding conditions one by one no longer makes sense. Consequently, eq, gt, lt, ge and le methods were removed. All PostSelect must now be constructed from a string or through merging two existing post-selection objects.

In-place apply permutation

Previously, apply_permutation method would create a new PostSelect object and return it. For consistency purpose, the new behavior modifies the data of the instance on which apply_permutation is called.

Circuit.generic_interferometer method was removed

The Circuit.generic_interferometer method has been deprecated since Perceval 0.10.0 and was removed from the code base (in order to avoid a circular import).

Please use the GenericInterferometer class (from perceval.components) directly. See Generic Interferometer and you can find a usage example in the Circuit Optimizer code reference.

Breaking changes in Perceval 0.11

postprocess

set_postprocess and clear_postprocess have been deprecated since Perceval 0.9.0 and are no more available.

See Simulation rework: processor

min_detected_photons_filter

We will now entice users to set a value for min_detected_photons_filter. Previously it was the number of photon in the input state (and still is until a future release). A warning will now be displayed when this value is not set. This value will progressively be mandatory for remote platforms.

This value will be mandatory in the future and will cause a error if not set.

Note

This change does not affect the perfect local simulation (local simulation without noise model)

Breaking changes in Perceval 0.10

The main changes between versions 0.9 and 0.10 comes from the migration of the StateVector code into our C++ library, Exqalibur.

StateVector

Iterate through a State Vector

State Vector is still a hash map (state, amplitude) but works a bit differently than a python dictionary.

State Vector keys, states, are obtained with method keys:

From version 0.9

>>> for state in state_vector:
>>>   assert state in state_vector

To version 0.10

>>> for state in state_vector.keys():
>>>   assert state in state_vector

State Vector items, (states, amplitude), are obtained by iterate directly through the state vector object:

From version 0.9

>>> for state, amplitude in state_vector.items():
>>>   assert state_vector[state] == amplitude

To version 0.10

>>> for state, amplitude in state_vector:
>>>   assert state_vector[state] == amplitude

Using numpy scalars in StateVector arithmetic

Exqalibur C++ package may interact badly with numpy types depending on the operand order in some arithmetic operations. Multiplying a numpy scalar (left operand) with a StateVector (right operand) fails as numpy has the priority on an operation it’s unable to perform correctly.

From version 0.9

>>> import numpy
>>> sv1 = numpy.int16(4) * state_vector
>>> sv2 = state_vector * numpy.int16(4)
>>> assert sv1 == sv2

To version 0.10

>>> import numpy
>>> # sv1 = numpy.int16(4) * state_vector # will raise a ValueError
>>> sv2 = state_vector * numpy.int16(4)

Note

StateVector will interact badly with any numpy scalar type

Shots in algorithms

When instantiating an algorithm class (Sampler, Analyzer) with a RemoteProcessor, the user now has to pass a positive integer value for the named parameter max_shots_per_call. Please note that this parameter name has to be typed in order to avoid potential signature errors.

>>> p = RemoteProcessor("sim:platform")
>>> sampler = Sampler(p, max_shots_per_call=10_000_000)

This parameter is also handled by local simulations.

Note

Probability amplitude back-ends used for sampling (e.g. using SLOS for a sample_count call) cannot estimate accurately the sample to shots ratio when converting probabilities to samples.

Parameter count was renamed to max_samples in methods samples and sample_count.

>>> sampler.samples(500)  # still works
>>> # sampler.samples(count=500)  # will not work anymore
>>> sampler.samples(max_samples=500)  # works

For additional information, see: Remote computing on Quandela Cloud

AnnotatedBasicState

AnnotatedBasicState has been deprecated since Perceval 0.7.0, it’s time to say goodbye.

See AnnotatedBasicState was deprecated

Breaking changes in Perceval 0.9

The main changes between versions 0.8 and 0.9 come from the simulation rework. The simulation code was split in three different layers: backends, simulators, processor. Some syntax was changed and your code might be broken. Note that if you were using the Processor layer to compute your simulations, the 0.8 syntax is still working with only two deprecated methods (see Simulation rework: processor).

Simulation rework: backends

The backend classes were reworked in order to let them do what they do best: perform a perfect simulation with a pure input fock state. The rest of the features (e.g. simulating a StateVector input, with distinguishable photons, etc.) were moved to a new class: the Simulator. Thus, former backend users should now preferably use the Simulator.

Backend syntax changes

If you still need to use the backend level, here are the following changes from version 0.8 to version 0.9:

From version 0.8

>>> backend_name = "SLOS"
>>> backend_type = pcvl.BackendFactory.get_backend(backend_name) # In 0.8, the BackendFactory would only be a mapping between a name and a type
>>> backend_obj = backend_type(circuit) # You'd have to instantiate the backend on the next line using the type
>>> pa = backend_obj.probampli(input_state, output_state) # You can then start simulating

To version 0.9

>>> backend_name = "SLOS"
>>> backend_obj = pcvl.BackendFactory.get_backend(backend_name) # In 0.9, the BackendFactory returns an empty backend instance
>>>
>>> from perceval.backends import SLOSBackend
>>> slos = SLOSBackend() # This is equivalent to using the BackendFactory
>>> slos_with_mask = SLOSBackend(mask=["0    0"], n=2) # You can also use the specifics of each backend when creating one
>>>
>>> slos.set_circuit(circuit) # Set a circuit first
>>> slos.set_input_state(input_state) # Input state has to be a Fock state (all indistinguishable photons)
>>> pa = slos.prob_amplitude(output_state) # Then you can start simulating

Note

As all simulation methods signature changed slightly, their name was changed too (e.g. probampli to prob_amplitude) in order to get an error message as soon as possible in your script. In API-break cases, it’s better to get an error than a seemingly working code with an unexpected behavior!

Note

Backends are more specialized than before. For instance, sample() cannot be called on SLOS and Naive anymore because they are natively probability amplitude computing backend. They however offer a way to compute the whole output probability distribution (prob_distribution() method) from which it is possible to sample. On a similar note, Clifford & Clifford backend is only capable of sampling (its native simulation method).

How to use the simulator layer

The Simulator is a versatile class which can simulate state evolution and sampling, using any of the probability amplitude capable backend for its computations.

>>> from perceval.simulators import Simulator
>>> from perceval.backends import SLOSBackend
>>>
>>> simulator = Simulator(SLOSBackend()) # Initialize a simulator instance with a backend object
>>> simulator.set_circuit(circuit)
>>> # Here input state can be a BasicState or a StateVector, with or without photon annotations
>>> pa = simulator.prob_amplitude(input_state, output_state)

The Simulator is also optimized to simulate a whole input distribution in one pass

>>> from perceval.components import Source
>>> from perceval.utils import BasicState
>>>
>>> # A simple example with a source-generated input distribution
>>> source = Source(losses=0.85, indistinguishability=0.9)
>>> input_distribution = source.generate_distribution(expected_input=BasicState([1, 0, 1, 0]))
{
  |0,0,0,0>: 0.7224999999999999
  |0,0,{_:0},0>: 0.1275
  |{_:0},0,0,0>: 0.1275
  |{_:0},0,{_:0},0>: 0.020250000000000004
  |{_:0},0,{_:1},0>: 0.002250000000000002
}
>>> simulator.set_min_detected_photons_filter(1)
>>> probs = simulator.probs_svd(input_distribution)
>>> print("physical performance:", probs["physical_perf"])
>>> print("output distribution:", probs["results"])
physical performance: 0.2775000000000001
output distribution: {
  |0,1,0,0>: 0.1456843866834125
  |0,0,1,0>: 0.1456843866834125
  |0,0,0,1>: 0.22972972972972971
  |1,0,0,0>: 0.39782041582236416
  |1,1,0,0>: 0.017550900698045487
  |1,0,1,0>: 0.017550900698045487
  |1,0,0,1>: 0.03510180139609097
  |0,2,0,0>: 0.00258340109361355
  |0,1,0,1>: 0.0027193695722247894
  |0,0,2,0>: 0.00258340109361355
  |0,0,1,1>: 0.0027193695722247894
  |0,1,1,0>: 0.00027193695722247914
}

See Simulator for the list of available simulation methods.

Simulation rework: processor

The Processor can be used exactly as in version 0.8. However, please note that set_postprocess and clear_postprocess methods have been deprecated in favor of set_postselection and clear_postselection.

set_postselection is more restrictive as it only allows PostSelect objects allowing Perceval to get rid of Python free functions / lambdas. We suggest you update your existing code base which is using set_postprocess with Python functions as it will be removed in an upcoming release without further notice.

See also: PostSelect code reference

Breaking changes in Perceval 0.8

Processors.mode_post_selection changes to min_detected_photons_filter

In Perceval 0.7, you could filter results by setting a minimum number of threshold detector “clicks” (which was translated, in simulators, to the number of modes with at least one photon)

>>> import perceval as pcvl
>>> p = pcvl.Processor("SLOS", 8, pcvl.Source(emission_probability=.8))
>>> p.with_input(pcvl.BasicState([1, 0, 1, 0, 0, 0, 0, 0]))
>>> p.mode_post_selection(2)  # In Perceval 0.7, Processor p would reject results with less than 2 modes with detections

Even though this filtering works well with QPU simulators and actual QPU acquisitions, it implied that more theoretical simulations was impacted by a threshold detection rule when they use perfect detectors. In this case, you could retrieve unexpected results.

Perceval introduces min_detected_photons_filter to improve its behavior. Updating to Perceval 0.8 and using min_detected_photons_filter as you would have used mode_post_selection, will not change results for threshold detections, and will improve them for perfect simulations (less states will be rejected, improving physical performance).

>>> p.min_detected_photons_filter(2)  # In Perceval 0.8, the new filter rejects states based on photon count

Breaking changes in Perceval 0.7

lib.phys and lib.symb have been removed

Base components, originally duplicated in the two libraries were merged in two modules perceval.components.unitary_components and perceval.components.non_unitary_components. One direct benefit of this change is that the beam splitter definition is now the same (see BS conventions), and does not depend on how it renders (see Display components).

>>> import perceval as pcvl
>>> from perceval.components.unitary_components import PS, BS, PERM
>>> import math
>>>
>>> c = pcvl.Circuit(2) // PS(math.pi) // BS() // PERM([1, 0]) // (1, PS(math.pi))

Display components

Initially, use of lib.symb or lib.phys was deciding how the circuit was displayed. Now, a skin system is available to use whichever representation you want.

>>> import perceval as pcvl
>>> from perceval.rendering import SymbSkin
>>>
>>> pcvl.pdisplay(c)  # defaults to PhysSkin, similar to lib.phys
>>> pcvl.pdisplay(c, skin=SymbSkin())  # Renders using SymbSkin, similar to lib.symb

see Circuit Rendering for more details.

BS conventions

lib.phys.BS used a different convention from lib.symb.BS. After merging both libs, only one BS class remains, handling 3 different conventions suited to any need. See Beam Splitter for details.

>>> from perceval.components.base_components import BS, BSConvention
>>>
>>> bs = BS()  # Defaults to Rx convention. Ideally, in an upcoming Perceval release, the default could be changed in a persistent user config.
>>> BS.H() == BS(convention=BSConvention.H)  # Both syntaxes give the same result.
>>> BS.Ry() == BS(convention=BSConvention.Ry)  # Same

This new BS class handles only theta (instead of a mutually exclusive theta or R) which is used differently from before: Half of theta is used when computing the unitary matrix (i.e. cos(theta/2) now, cos(theta) before).

Also, the new BS can be configured with 4 phases, one on each mode (phi_tl, phi_tr, phi_bl, phi_br) corresponding respectively to top left, top right, bottom left and bottom right arms of the beam splitter.

There is no direct conversion from former symb.BS or phys.BS.

  • BS conventions - existing code:

In all the existing code base, phys.BS were replaced by BS.H and symb.BS by BS.Rx which have the same unitary matrices when no phase are applied to them.

Create a backend instance

Originally, you would call

>>> backend_type = BackendFactory().get_backend(backend_name)  # For instance backend_name = "SLOS"
>>> simu_backend = backend_type(circuit)

While this is still functional, this can also be misleading. Indeed, simulation backends can provide features that you cannot measure with actual QPU - typically the probability amplitude. This is good for developing theoretical algorithms but using these will not port to actual QPUs. We recommend using the class Processor by default.

AnnotatedBasicState was deprecated

Please use BasicState instead which holds every feature previously held by AnnotatedBasicState

Processor definition and composition

Perceval is getting more and more Processor-centric as we implement more features. The Processor class has got some serious refactoring. You may find examples of Processor created from scratch in perceval.components.core_catalog content. You may use several processors / circuits and compose them : a good example is the QiskitConvert convert method implementation.

Access to circuit parameters

It was possible to access a named parameters on a circuit using [] notation:

>>> c['phi']

This has been replaced by explicit use of params accessor:

>>> c.param('phi')

The __getitem__ notation is now used to access components in a circuit (see Accessing components in a circuit).

New Source in Perceval 0.7.3

A new source model has been introduced in Perceval 0.7.3. The Source class initialization parameters have changed and imperfect simulated sources will return results closer to the actual photonic sources which are used in the QPUs. Backward compatibility with pre-0.7.3 sources is broken.

  • brightness was replaced by emission_probability. Balanced losses from the source output to the circuit output can be modelled with losses parameter.

  • purity and purity_model were respectively replaced by multiphoton_component and multiphoton_model. purity represented the ratio of time when photon is emitted alone whereas multiphoton_component is the \(g^{(2)}\). There is no direct conversion from the former purity to \(g^{(2)}\), note however that the greater the purity, the lower the \(g^{(2)}\).

  • The default distinguishability of multiple emitted photons changed from indistinguishable to distinguishable.

>>> source = pcvl.Source(brightness=0.3, purity=0.95, purity_model="distinguishable")

can be changed to (without returning the same results):

>>> source = pcvl.Source(emission_probability=0.3, multiphoton_component=0.05)

See Source class reference for more information.