diff --git a/docs/source/reference/simulators/noisy_sampling_simulator.rst b/docs/source/reference/simulators/noisy_sampling_simulator.rst index f487702a3..50236e02c 100644 --- a/docs/source/reference/simulators/noisy_sampling_simulator.rst +++ b/docs/source/reference/simulators/noisy_sampling_simulator.rst @@ -46,3 +46,17 @@ Using a NoisySamplingSimulator .. autoclass:: perceval.simulators.NoisySamplingSimulator :members: :inherited-members: + + +ExqaliburNoisySamplingSimulator +=============================== + +The :code:`ExqaliburNoisySamplingSimulator` does the same things as the :code:`NoisySamplingSimulator`, but its methods +are implemented in exqalibur so it runs faster. + +Also, this simulator requires the backend to be a wrapper around a pure exqalibur sampling backend. +The :ref:`CliffordClifford2017` backend is an example of such a backend. + +.. autoclass:: perceval.simulators.ExqaliburNoisySamplingSimulator + :members: + :inherited-members: diff --git a/perceval/backends/__init__.py b/perceval/backends/__init__.py index 84e2736e0..6817c50ec 100644 --- a/perceval/backends/__init__.py +++ b/perceval/backends/__init__.py @@ -27,7 +27,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from ._abstract_backends import ABackend, ASamplingBackend, AStrongSimulationBackend, IFFBackend +from ._abstract_backends import ABackend, ASamplingBackend, AStrongSimulationBackend, IFFBackend, ExqaliburBackendWrapper from ._clifford2017 import Clifford2017Backend from ._mps import MPSBackend from ._naive import NaiveBackend diff --git a/perceval/backends/_abstract_backends.py b/perceval/backends/_abstract_backends.py index 58a7bf1d7..13f68ba89 100644 --- a/perceval/backends/_abstract_backends.py +++ b/perceval/backends/_abstract_backends.py @@ -233,3 +233,10 @@ def set_feed_forward(self, components: list[tuple[tuple, AComponent]], m: int) - :param m: The number of modes in the circuit. """ pass + + +class ExqaliburBackendWrapper(ABC): + + @abstractmethod + def get_exqalibur_backend(self): + pass diff --git a/perceval/backends/_clifford2017.py b/perceval/backends/_clifford2017.py index 4eb1114bb..563707e47 100644 --- a/perceval/backends/_clifford2017.py +++ b/perceval/backends/_clifford2017.py @@ -30,10 +30,10 @@ import exqalibur as xq from perceval.utils import FockState from perceval.components import ACircuit -from ._abstract_backends import ASamplingBackend +from ._abstract_backends import ASamplingBackend, ExqaliburBackendWrapper -class Clifford2017Backend(ASamplingBackend): +class Clifford2017Backend(ASamplingBackend, ExqaliburBackendWrapper): def __init__(self): super().__init__() self._clifford = xq.Clifford2017() @@ -59,3 +59,6 @@ def samples(self, count: int): @property def name(self) -> str: return "CliffordClifford2017" + + def get_exqalibur_backend(self): + return self._clifford diff --git a/perceval/backends/_slap.py b/perceval/backends/_slap.py index 96fee3522..83199b216 100644 --- a/perceval/backends/_slap.py +++ b/perceval/backends/_slap.py @@ -32,10 +32,10 @@ from perceval.components import (ACircuit, AFFConfigurator, FFCircuitProvider, Experiment, IDetector, DetectionType, AComponent, Circuit, Barrier, FFConfigurator) -from ._abstract_backends import AStrongSimulationBackend, IFFBackend +from ._abstract_backends import AStrongSimulationBackend, IFFBackend, ExqaliburBackendWrapper -class SLAPBackend(AStrongSimulationBackend, IFFBackend): +class SLAPBackend(AStrongSimulationBackend, IFFBackend, ExqaliburBackendWrapper): def __init__(self, mask=None): super().__init__() @@ -186,3 +186,6 @@ def set_feed_forward(self, components: list[tuple[tuple, AComponent]], m: int) - self.set_circuit(main_unitary) for mp in maps: self._slap.add_feed_forward_config(mp) + + def get_exqalibur_backend(self): + return self._slap diff --git a/perceval/runtime/processor.py b/perceval/runtime/processor.py index 2485bd297..2255d76e1 100644 --- a/perceval/runtime/processor.py +++ b/perceval/runtime/processor.py @@ -161,10 +161,16 @@ def linear_circuit(self, flatten: bool = False) -> Circuit: def samples(self, max_samples: int, max_shots: int = None, progress_callback=None) -> dict: self.check_min_detected_photons_filter() - from perceval.simulators import NoisySamplingSimulator - from perceval.backends import ASamplingBackend + + # TODO: move imports on top of this file after moving Processor to runtime + from perceval.backends import ExqaliburBackendWrapper, ASamplingBackend + from perceval.simulators import ExqaliburNoisySamplingSimulator, NoisySamplingSimulator + assert isinstance(self.backend, ASamplingBackend), "A sampling backend is required to call samples method" - sampling_simulator = NoisySamplingSimulator(self.backend) + if isinstance(self.backend, ExqaliburBackendWrapper): + sampling_simulator = ExqaliburNoisySamplingSimulator(self.backend) + else: + sampling_simulator = NoisySamplingSimulator(self.backend) sampling_simulator.sleep_between_batches = 0 # Remove sleep time between batches of samples in local simulation sampling_simulator.set_circuit(self.linear_circuit()) sampling_simulator.set_selection( diff --git a/perceval/serialization/_detector_serialization.py b/perceval/serialization/_detector_serialization.py index 402237598..cd29fd17f 100644 --- a/perceval/serialization/_detector_serialization.py +++ b/perceval/serialization/_detector_serialization.py @@ -26,6 +26,8 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +from multipledispatch import dispatch + from perceval.serialization import _schema_circuit_pb2 as pb from perceval.components import BSLayeredPPNR, Detector @@ -63,3 +65,17 @@ def deserialize_detector(pb_d: pb.Detector) -> Detector: detector = Detector(n_wires, max_detections, wire_efficiency) detector.name = pb_d.name return detector + + +@dispatch(BSLayeredPPNR) +def serialize_idetector(detector): + pb_detector = pb.IDetector() + pb_detector.ppnr.CopyFrom(serialize_bs_layer(detector)) + return pb_detector + + +@dispatch(Detector) +def serialize_idetector(detector): + pb_detector = pb.IDetector() + pb_detector.detector.CopyFrom(serialize_detector(detector)) + return pb_detector diff --git a/perceval/serialization/_experiment_serialization.py b/perceval/serialization/_experiment_serialization.py index 50c3ef2ce..beb4fdc5e 100644 --- a/perceval/serialization/_experiment_serialization.py +++ b/perceval/serialization/_experiment_serialization.py @@ -29,12 +29,12 @@ from multipledispatch import dispatch -from perceval.components import Experiment, Herald, Port, APort, IDetector, BSLayeredPPNR, Detector, AComponent +from perceval.components import Experiment, Herald, Port, APort, IDetector, AComponent from perceval.serialization import _schema_circuit_pb2 as pb from perceval.serialization import serialize from perceval.serialization._circuit_serialization import serialize_port, serialize_herald, ComponentSerializer from perceval.serialization._constants import VALUE_NOT_SET -from perceval.serialization._detector_serialization import serialize_detector, serialize_bs_layer +from perceval.serialization._detector_serialization import serialize_idetector class ExperimentSerializer: @@ -92,24 +92,10 @@ def _serialize_ports(self, in_ports: dict[APort, list[int]], out_ports: dict[APo pb_port = self._serialize_port(port) self._serialized.output_ports[modes[0]].CopyFrom(pb_port) - @staticmethod - @dispatch(BSLayeredPPNR) - def _serialize_detector(detector): - pb_detector = pb.IDetector() - pb_detector.ppnr.CopyFrom(serialize_bs_layer(detector)) - return pb_detector - - @staticmethod - @dispatch(Detector) - def _serialize_detector(detector): - pb_detector = pb.IDetector() - pb_detector.detector.CopyFrom(serialize_detector(detector)) - return pb_detector - def _serialize_detectors(self, detectors: list[IDetector]): for i, detector in enumerate(detectors): if detector is not None: - pb_detector = self._serialize_detector(detector) + pb_detector = serialize_idetector(detector) self._serialized.detectors[i].CopyFrom(pb_detector) def _serialize_components(self, components: list[tuple[tuple, AComponent]]): diff --git a/perceval/serialization/serialize_binary.py b/perceval/serialization/serialize_binary.py index 4bfe29fcd..1471795c2 100644 --- a/perceval/serialization/serialize_binary.py +++ b/perceval/serialization/serialize_binary.py @@ -36,16 +36,28 @@ from multipledispatch import dispatch from ._circuit_serialization import serialize_circuit +from ._detector_serialization import serialize_idetector from ._matrix_serialization import serialize_matrix from perceval.components.linear_circuit import ACircuit +from perceval.components.detector import IDetector from perceval.utils.matrix import Matrix @dispatch(ACircuit) -def serialize_binary(circuit: ACircuit): +def serialize_binary(circuit: ACircuit) -> bytes: return serialize_circuit(circuit).SerializeToString() @dispatch(Matrix) -def serialize_binary(matrix: Matrix): +def serialize_binary(matrix: Matrix) -> bytes: return serialize_matrix(matrix).SerializeToString() + + +@dispatch(IDetector) +def serialize_binary(detector: IDetector) -> bytes: + return serialize_idetector(detector).SerializeToString() + + +@dispatch(list) +def serialize_binary(l: list) -> list[bytes]: + return [serialize_binary(o) for o in l] diff --git a/perceval/simulators/__init__.py b/perceval/simulators/__init__.py index 6241826a7..fc2d4d718 100644 --- a/perceval/simulators/__init__.py +++ b/perceval/simulators/__init__.py @@ -33,5 +33,5 @@ from .simulator import Simulator from .simulator_factory import SimulatorFactory from .stepper import Stepper -from .noisy_sampling_simulator import NoisySamplingSimulator +from .noisy_sampling_simulator import NoisySamplingSimulator, ExqaliburNoisySamplingSimulator from .feed_forward_simulator import FFSimulator diff --git a/perceval/simulators/noisy_sampling_simulator.py b/perceval/simulators/noisy_sampling_simulator.py index e254f9677..e97fd3020 100644 --- a/perceval/simulators/noisy_sampling_simulator.py +++ b/perceval/simulators/noisy_sampling_simulator.py @@ -26,21 +26,189 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +from abc import ABC, abstractmethod import math import time import sys from collections import defaultdict from typing import Callable -from perceval.backends import ASamplingBackend +import exqalibur as xq + +from perceval.backends import ASamplingBackend, ExqaliburBackendWrapper from perceval.components import ACircuit, IDetector, get_detection_type, DetectionType, check_heralds_detectors, Source from perceval.utils import BasicState, FockState, StateVector, NoisyFockState, BSCount, BSSamples, SVDistribution, PostSelect, \ samples_to_sample_count +from perceval.serialization.serialize_binary import serialize_binary from perceval.utils.logging import get_logger, channel from perceval.runtime import cancel_requested from ._simulate_detectors import simulate_detectors_sample +class ASamplingSimulator(ABC): + + def __init__(self, sampling_backend: ASamplingBackend): + self._backend = sampling_backend + self._min_detected_photons_filter = 0 + self._postselect: PostSelect = PostSelect() + self._heralds: dict = {} + self._keep_heralds = True + self._detectors = None + self._compute_physical_logical_perf = False + + @property + def min_detected_photons_filter(self): + """ + :return: The minimum number of detected photons filter. + Counts both user-defined minimum number of photons and heralds + """ + return self._min_detected_photons_filter + sum(self._heralds.values()) + + def set_detectors(self, detector_list: list[IDetector]): + """ + :param detector_list: A list of detectors to simulate + """ + self._detectors = detector_list + + def keep_heralds(self, value: bool): + """ + Tells the simulator to keep or discard ancillary modes in output states + + :param value: True to keep ancillaries/heralded modes, False to discard them (default is keep). + """ + self._keep_heralds = value + + def compute_physical_logical_perf(self, value: bool): + """ + Tells the simulator to compute or not the physical and logical performances when possible + + :param value: True to compute the physical and logical performances, False otherwise. + """ + self._compute_physical_logical_perf = value + + def set_heralds(self, value: dict[int, int]): + self._heralds = value + + def set_postselect(self, postselect: PostSelect): + self._postselect = postselect + + def set_selection(self, + min_detected_photons_filter: int = None, + postselect: PostSelect = None, + heralds: dict = None): + """Set multiple selection filters at once to remove unwanted states from computed output distribution + + :param min_detected_photons_filter: minimum number of detected photons in the output distribution + :param postselect: a post-selection function + :param heralds: expected detections (heralds). Only corresponding states will be selected, others are filtered + out. Mapping of heralds. For instance `{5: 0, 6: 1}` means 0 photon is expected on mode 5 and 1 + on mode 6. + """ + if min_detected_photons_filter is not None: + self.set_min_detected_photons_filter(min_detected_photons_filter) + if postselect is not None: + self.set_postselect(postselect) + if heralds is not None: + self.set_heralds(heralds) + + def set_circuit(self, circuit: ACircuit): + """ + Set the circuit to simulate the sampling on + + :param circuit: A unitary circuit + """ + self._backend.set_circuit(circuit) + + def set_min_detected_photons_filter(self, value: int): + """ + Set the physical detection filter. Any output state with less than this threshold gets discarded. + + :param value: Minimal photon count in output states of interest. + """ + self._min_detected_photons_filter = value + + @abstractmethod + def samples(self, + svd: SVDistribution | tuple[Source, FockState], + max_samples: int, + max_shots: int = None, + progress_callback: Callable[[float, str], None | dict] = None) -> dict: + """ + Run a noisy sampling simulation and retrieve the results + + :param svd: The noisy input, expressed as a mixed state, + or a tuple containing the source and the perfect input state + :param max_samples: Max expected samples of interest in the results + :param max_shots: Shots limit before the sampling ends (you might get fewer samples than expected) + :param progress_callback: A progress callback + :return: A dictionary of the form { "results": BSSamples, "physical_perf": float, "logical_perf": float } + + - results is the post-selected output state sample stream + - physical_perf is the performance computed from the detected photon filter + - logical_perf is the performance computed from the post-selection + """ + + def sample_count(self, + svd: SVDistribution | tuple[Source, BasicState], + max_samples: int, + max_shots: int = None, + progress_callback: Callable[[float, str], None | dict] = None) -> dict: + """ + Run a noisy sampling simulation and retrieve the results + + :param svd: The noisy input, expressed as a mixed state, + or a tuple containing the source and the perfect input state + :param max_samples: Max expected samples of interest in the results + :param max_shots: Shots limit before the sampling ends (you might get fewer samples than expected) + :param progress_callback: A progress callback + :return: A dictionary of the form { "results": BSCount, "physical_perf": float, "logical_perf": float } + + - results is the post-selected output state sample count + - physical_perf is the performance computed from the detected photon filter + - logical_perf is the performance computed from the post-selection + """ + sampling = self.samples(svd, max_samples, max_shots, progress_callback) + sampling['results'] = samples_to_sample_count(sampling['results']) + return sampling + + def log_resources(self, method: str, extra_parameters: dict): + """Log resources of the noisy sampling simulator + + :param method: name of the method used + :param extra_parameters: extra parameters to log + + Extra parameter can be: + + - max_samples + - max_shots + """ + extra_parameters = {key: value for key, value in extra_parameters.items() if value is not None} + my_dict = { + 'layer': type(self).__name__, + 'backend': self._backend.name, + 'm': self._backend._circuit.m, + 'method': method + } + if extra_parameters: + my_dict.update(extra_parameters) + get_logger().log_resources(my_dict) + + def format_results(self, results, physical_perf, logical_perf): + """ + Format the simulation results by computing the global performance, and returning the physical and + logical performances only if needed. + + :param results: the simulation results + :param physical_perf: the physical performance + :param logical_perf: the logical performance + """ + result = {'results': results, 'global_perf': physical_perf * logical_perf} + if self._compute_physical_logical_perf: + result['physical_perf'] = physical_perf + result['logical_perf'] = logical_perf + return result + + class SamplesProvider: def __init__(self, sampling_backend: ASamplingBackend): @@ -129,7 +297,7 @@ def sample_from(self, input_state: FockState) -> BasicState: return self._pools[input_state].pop() -class NoisySamplingSimulator: +class NoisySamplingSimulator(ASamplingSimulator): """ Simulates a sampling, using a perfect sampling algorithm. It is used to take advantage of a parallel sampling algorithm, by computing multiple output states at once, while taking noise and post-processing (heralds, @@ -139,59 +307,8 @@ class NoisySamplingSimulator: """ def __init__(self, sampling_backend: ASamplingBackend): - self._backend = sampling_backend - self._min_detected_photons_filter = 0 - self._postselect: PostSelect = PostSelect() - self._heralds: dict = {} - self._keep_heralds = True + super().__init__(sampling_backend) self.sleep_between_batches = 0.2 # sleep duration (in s) between two batches of samples - self._detectors = None - self._compute_physical_logical_perf = False - - @property - def min_detected_photons_filter(self): - return self._min_detected_photons_filter + sum(self._heralds.values()) - - def set_detectors(self, detector_list: list[IDetector]): - """ - :param detector_list: A list of detectors to simulate - """ - self._detectors = detector_list - - def keep_heralds(self, value: bool): - """ - Tells the simulator to keep or discard ancillary modes in output states - - :param value: True to keep ancillaries/heralded modes, False to discard them (default is keep). - """ - self._keep_heralds = value - - def compute_physical_logical_perf(self, value: bool): - """ - Tells the simulator to compute or not the physical and logical performances when possible - - :param value: True to compute the physical and logical performances, False otherwise. - """ - self._compute_physical_logical_perf = value - - def set_selection(self, - min_detected_photons_filter: int = None, - postselect: PostSelect = None, - heralds: dict = None): - """Set multiple selection filters at once to remove unwanted states from computed output distribution - - :param min_detected_photons_filter: minimum number of detected photons in the output distribution - :param postselect: a post-selection function - :param heralds: expected detections (heralds). Only corresponding states will be selected, others are filtered - out. Mapping of heralds. For instance `{5: 0, 6: 1}` means 0 photon is expected on mode 5 and 1 - on mode 6. - """ - if min_detected_photons_filter is not None: - self._min_detected_photons_filter = min_detected_photons_filter - if postselect is not None: - self._postselect = postselect - if heralds is not None: - self._heralds = heralds def _state_selected(self, state: BasicState) -> bool: """ @@ -204,22 +321,6 @@ def _state_selected(self, state: BasicState) -> bool: return self._postselect(state) return True - def set_circuit(self, circuit: ACircuit): - """ - Set the circuit to simulate the sampling on - - :param circuit: A unitary circuit - """ - self._backend.set_circuit(circuit) - - def set_min_detected_photons_filter(self, value: int): - """ - Set the physical detection filter. Any output state with less than this threshold gets discarded. - - :param value: Minimal photon count in output states of interest. - """ - self._min_detected_photons_filter = value - def _perfect_sampling_no_selection( self, input_state: BasicState, @@ -478,11 +579,67 @@ def samples(self, 'n': n, 'max_samples': max_samples, 'max_shots': max_shots}) return res - def sample_count(self, - svd: SVDistribution | tuple[Source, BasicState], - max_samples: int, - max_shots: int = None, - progress_callback: callable = None) -> dict: + +class ExqaliburNoisySamplingSimulator(ASamplingSimulator): + """ + Simulates a sampling, using a perfect sampling algorithm. It is used to take advantage of a parallel sampling + algorithm, by computing multiple output states at once, while taking noise and post-processing (heralds, + post-selection, detector characteristics) into account. + + This particular sampling simulator runs exclusively using exqalibur methods for an even faster. + + :param sampling_backend: Instance of a sampling-capable exqalibur-wrapped back-end + """ + + def __init__(self, sampling_backend: ExqaliburBackendWrapper): + assert isinstance(sampling_backend, ExqaliburBackendWrapper) and isinstance(sampling_backend, ASamplingBackend),\ + "The given backend must be an Exqalibur backend wrapper and a Sampling backend." + super().__init__(sampling_backend) + self._sim = xq.SamplingSimulator(sampling_backend.get_exqalibur_backend()) + + def set_detectors(self, detector_list: list[IDetector | None] | None): + super().set_detectors(detector_list) + if detector_list is None: + self._sim.set_detectors([]) + else: + l = [] + for d in detector_list: + l.append(serialize_binary(d) if d is not None else "") + self._sim.set_detectors(l) + + def keep_heralds(self, value: bool): + super().keep_heralds(value) + self._sim.keep_heralds = value + + def compute_physical_logical_perf(self, value: bool): + super().compute_physical_logical_perf(value) + self._sim.differentiate_perf = value + + def set_min_detected_photons_filter(self, value: int): + super().set_min_detected_photons_filter(value) + self._sim.min_photons_filter = value + + def set_heralds(self, value: dict[int, int]): + super().set_heralds(value) + self._sim.heralds = value + + def set_postselect(self, postselect: PostSelect): + super().set_postselect(postselect) + self._sim.postselect = postselect + + @property + def sleep_between_batches(self): + return self._sim.callback_interval + + @sleep_between_batches.setter + def sleep_between_batches(self, value): + self._sim.callback_interval = value + + def samples(self, + svd: SVDistribution | tuple[Source, FockState], + max_samples: int, + max_shots: int = None, + progress_callback: Callable[[float, str], None | dict] = None) -> dict: """ Run a noisy sampling simulation and retrieve the results @@ -491,49 +648,29 @@ def sample_count(self, :param max_samples: Max expected samples of interest in the results :param max_shots: Shots limit before the sampling ends (you might get fewer samples than expected) :param progress_callback: A progress callback - :return: A dictionary of the form { "results": BSCount, "physical_perf": float, "logical_perf": float } + :return: A dictionary of the form { "results": BSSamples, "physical_perf": float, "logical_perf": float } - - results is the post-selected output state sample count + - results is the post-selected output state sample stream - physical_perf is the performance computed from the detected photon filter - logical_perf is the performance computed from the post-selection """ - sampling = self.samples(svd, max_samples, max_shots, progress_callback) - sampling['results'] = samples_to_sample_count(sampling['results']) - return sampling - - def log_resources(self, method: str, extra_parameters: dict): - """Log resources of the noisy sampling simulator - - :param method: name of the method used - :param extra_parameters: extra parameters to log + if not check_heralds_detectors(self._heralds, self._detectors): + return self.format_results(BSSamples(), 1, 0) - Extra parameter can be: + if progress_callback is not None: + self._sim.progress_callback = lambda prog, msg: cancel_requested(progress_callback(prog, msg)) + else: + self._sim.progress_callback = None - - max_samples - - max_shots - """ - extra_parameters = {key: value for key, value in extra_parameters.items() if value is not None} - my_dict = { - 'layer': 'NoisySamplingSimulator', - 'backend': self._backend.name, - 'm': self._backend._circuit.m, - 'method': method - } - if extra_parameters: - my_dict.update(extra_parameters) - get_logger().log_resources(my_dict) + if isinstance(svd, tuple): + n = svd[1].n + samples = self._sim.samples(svd[0]._source, svd[1], max_samples, max_shots) + else: + n = svd.n_max + samples = self._sim.samples(svd, max_samples, max_shots) - def format_results(self, results, physical_perf, logical_perf): - """ - Format the simulation results by computing the global performance, and returning the physical and - logical performances only if needed. + res = self.format_results(samples, self._sim.get_physical_perf(), self._sim.get_logical_perf()) - :param results: the simulation results - :param physical_perf: the physical performance - :param logical_perf: the logical performance - """ - result = {'results': results, 'global_perf': physical_perf * logical_perf} - if self._compute_physical_logical_perf: - result['physical_perf'] = physical_perf - result['logical_perf'] = logical_perf - return result + self.log_resources(sys._getframe().f_code.co_name, { + 'n': n, 'max_samples': max_samples, 'max_shots': max_shots}) + return res diff --git a/tests/simulators/test_noisy_sampling_simulator.py b/tests/simulators/test_noisy_sampling_simulator.py index dc6d588fc..2b1469807 100644 --- a/tests/simulators/test_noisy_sampling_simulator.py +++ b/tests/simulators/test_noisy_sampling_simulator.py @@ -27,7 +27,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from perceval.simulators import NoisySamplingSimulator +from perceval.simulators import NoisySamplingSimulator, ExqaliburNoisySamplingSimulator from perceval.backends import Clifford2017Backend from perceval.components import Unitary, Source, BS, Detector from perceval.utils import Matrix, BasicState, SVDistribution @@ -35,9 +35,10 @@ import pytest +@pytest.mark.parametrize("use_exqalibur", [True, False]) @pytest.mark.parametrize("max_samples, max_shots", [(100, None), (100, 10), (10, 100)]) -def test_perfect_sampling(max_samples, max_shots): - sim = _build_noisy_simulator(8) +def test_perfect_sampling(max_samples, max_shots, use_exqalibur): + sim = _build_noisy_simulator(8, use_exqalibur) input_state = SVDistribution(BasicState([1, 0]*4)) sim.compute_physical_logical_perf(True) sampling = sim.samples(input_state, max_samples, max_shots) @@ -48,9 +49,10 @@ def test_perfect_sampling(max_samples, max_shots): assert output_state.n == 4 +@pytest.mark.parametrize("use_exqalibur", [True, False]) @pytest.mark.parametrize("max_samples, max_shots", [(100, None), (100, 10), (10, 100)]) -def test_perfect_sampling_source(max_samples, max_shots): - sim = _build_noisy_simulator(8) +def test_perfect_sampling_source(max_samples, max_shots, use_exqalibur): + sim = _build_noisy_simulator(8, use_exqalibur) input_state = BasicState([1, 0]*4) sim.compute_physical_logical_perf(True) sampling = sim.samples((Source(), input_state), max_samples, max_shots) @@ -61,31 +63,37 @@ def test_perfect_sampling_source(max_samples, max_shots): assert output_state.n == 4 -def _build_noisy_simulator(size: int): +def _build_noisy_simulator(size: int, use_exqalibur: bool = False): c = Unitary(Matrix.random_unitary(size)) - sim = NoisySamplingSimulator(Clifford2017Backend()) + if use_exqalibur: + sim = ExqaliburNoisySamplingSimulator(Clifford2017Backend()) + else: + sim = NoisySamplingSimulator(Clifford2017Backend()) sim.set_circuit(c) return sim -def test_sample_0_samples(): - sim = _build_noisy_simulator(6) +@pytest.mark.parametrize("use_exqalibur", [True, False]) +def test_sample_0_samples(use_exqalibur): + sim = _build_noisy_simulator(6, use_exqalibur) source = Source(losses=0.8, indistinguishability=0.75, multiphoton_component=0.05) input_state = source.generate_distribution(BasicState([1, 0] * 3)) sampling = sim.samples(input_state, 0) assert len(sampling['results']) == 0 -def test_sample_0_samples_source(): - sim = _build_noisy_simulator(6) +@pytest.mark.parametrize("use_exqalibur", [True, False]) +def test_sample_0_samples_source(use_exqalibur): + sim = _build_noisy_simulator(6, use_exqalibur) source = Source(losses=0.8, indistinguishability=0.75, multiphoton_component=0.05) input_state = BasicState([1, 0] * 3) sampling = sim.samples((source, input_state), 0) assert len(sampling['results']) == 0 -def test_noisy_sampling(): - sim = _build_noisy_simulator(6) +@pytest.mark.parametrize("use_exqalibur", [True, False]) +def test_noisy_sampling(use_exqalibur): + sim = _build_noisy_simulator(6, use_exqalibur) sim.compute_physical_logical_perf(True) source = Source(losses=0.8, indistinguishability=0.75, multiphoton_component=0.05) input_state = source.generate_distribution(BasicState([1, 0] * 3)) @@ -107,8 +115,9 @@ def test_noisy_sampling(): assert sampling['results'].total() == 100 -def test_noisy_sampling_source(): - sim = _build_noisy_simulator(6) +@pytest.mark.parametrize("use_exqalibur", [True, False]) +def test_noisy_sampling_source(use_exqalibur): + sim = _build_noisy_simulator(6, use_exqalibur) sim.compute_physical_logical_perf(True) source = Source(losses=0.8, indistinguishability=0.75, multiphoton_component=0.05) input_state = BasicState([1, 0] * 3) @@ -130,8 +139,9 @@ def test_noisy_sampling_source(): assert sampling['results'].total() == 100 -def test_noisy_sampling_with_heralds(): - sim = _build_noisy_simulator(6) +@pytest.mark.parametrize("use_exqalibur", [True, False]) +def test_noisy_sampling_with_heralds(use_exqalibur): + sim = _build_noisy_simulator(6, use_exqalibur) sim.compute_physical_logical_perf(True) source = Source(losses=0.8, indistinguishability=0.75, multiphoton_component=0.05) input_state = source.generate_distribution(BasicState([1, 0] * 3)) @@ -153,8 +163,12 @@ def test_noisy_sampling_with_heralds(): assert len(output_state) == 5 # The ancillary mode was removed from all output states -def test_noisy_sampling_with_detectors(): - simulator = NoisySamplingSimulator(Clifford2017Backend()) +@pytest.mark.parametrize("use_exqalibur", [True, False]) +def test_noisy_sampling_with_detectors(use_exqalibur): + if use_exqalibur: + simulator = ExqaliburNoisySamplingSimulator(Clifford2017Backend()) + else: + simulator = NoisySamplingSimulator(Clifford2017Backend()) simulator.set_circuit(BS()) simulator.set_detectors([Detector.pnr(), Detector.pnr()]) simulator.compute_physical_logical_perf(True)