From b575cfa76f470c50080497ef9ff0bba1b8cc6c93 Mon Sep 17 00:00:00 2001 From: Benoit Fanchon Date: Tue, 16 Dec 2025 19:18:13 +0100 Subject: [PATCH 01/11] SLOS C++ --- perceval/backends/__init__.py | 6 +- perceval/backends/_slap.py | 28 ++++----- perceval/backends/_slos_cpp.py | 104 ++++++++++++++++++++++++++++++++ perceval/backends/_slos_v2.py | 104 ++++++++++++++++++++++++++++++++ tests/backends/test_backends.py | 20 +++--- 5 files changed, 237 insertions(+), 25 deletions(-) create mode 100644 perceval/backends/_slos_cpp.py create mode 100644 perceval/backends/_slos_v2.py diff --git a/perceval/backends/__init__.py b/perceval/backends/__init__.py index 84e2736e..ee88429a 100644 --- a/perceval/backends/__init__.py +++ b/perceval/backends/__init__.py @@ -34,6 +34,8 @@ from ._naive_approx import NaiveApproxBackend from ._slos import SLOSBackend from ._slap import SLAPBackend +from ._slos_v2 import SLOSV2Backend +from ._slos_cpp import SLOSCPPBackend BACKEND_LIST = { @@ -42,7 +44,9 @@ "Naive": NaiveBackend, "NaiveApprox": NaiveApproxBackend, "SLAP": SLAPBackend, - "SLOS": SLOSBackend + "SLOS": SLOSBackend, + "SLOS_CPP": SLOSCPPBackend, + "SLOS_V2": SLOSV2Backend } diff --git a/perceval/backends/_slap.py b/perceval/backends/_slap.py index 96fee352..d116e60d 100644 --- a/perceval/backends/_slap.py +++ b/perceval/backends/_slap.py @@ -45,41 +45,41 @@ def __init__(self, mask=None): def set_circuit(self, circuit: ACircuit): super().set_circuit(circuit) # Computes circuit unitary as _umat - self._slap.reset_feed_forward() + # self._slap.reset_feed_forward() self._slap.set_unitary(self._umat) def _init_mask(self): super()._init_mask() - if self._mask: - self._slap.set_mask(self._mask) - else: - self._slap.reset_mask() + self._slap.set_mask(self._mask) def prob_amplitude(self, output_state: FockState) -> complex: istate = self._input_state - all_pa = self._slap.all_prob_ampli(istate) + self._slap.set_input_state(self._input_state) + all_pa = self._slap.all_amplitudes() if self._mask: return all_pa[xq.FSArray(self._input_state.m, self._input_state.n, self._mask).find(output_state)] else: return all_pa[xq.FSArray(self._input_state.m, self._input_state.n).find(output_state)] def prob_distribution(self) -> BSDistribution: - return self._slap.prob_distribution(self._input_state) + self._slap.set_input_state(self._input_state) + return self._slap.distribution() + + def all_prob_ampli(self): + self._slap.set_input_state(self._input_state) + return self._slap.all_amplitudes() @property def name(self) -> str: return "SLAP" def all_prob(self, input_state: FockState = None): - if input_state is not None: - self.set_input_state(input_state) - else: - input_state = self._input_state - return self._slap.all_prob(input_state) + self._slap.set_input_state(input_state or self._input_state) + return self._slap.all_probabilities() def evolve(self) -> StateVector: - istate = self._input_state - all_pa = self._slap.all_prob_ampli(istate) + self._slap.set_input_state(self._input_state) + all_pa = self._slap.all_amplitudes() res = StateVector() for output_state, pa in zip(self._get_iterator(self._input_state), all_pa): res += output_state * pa diff --git a/perceval/backends/_slos_cpp.py b/perceval/backends/_slos_cpp.py new file mode 100644 index 00000000..ccaeea85 --- /dev/null +++ b/perceval/backends/_slos_cpp.py @@ -0,0 +1,104 @@ +# MIT License +# +# Copyright (c) 2022 Quandela +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# As a special exception, the copyright holders of exqalibur library give you +# permission to combine exqalibur with code included in the standard release of +# Perceval under the MIT license (or modified versions of such code). You may +# copy and distribute such a combined system following the terms of the MIT +# license for both exqalibur and Perceval. This exception for the usage of +# exqalibur is limited to the python bindings used by Perceval. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# 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. +import exqalibur as xq +from perceval.utils import FockState, BSDistribution, StateVector +from perceval.components import ACircuit +from perceval.utils.states import BasicState + +from ._abstract_backends import AStrongSimulationBackend + + +class SLOSCPPBackend(AStrongSimulationBackend): + + def __init__(self, mask=None): + super().__init__() + self._slos = xq.SLOS() + self._fock_space = None + if mask is not None: + self.set_mask(mask) + + def set_circuit(self, circuit: ACircuit): + super().set_circuit(circuit) # Computes circuit unitary as _umat + self._slos.set_unitary(self._umat) + + def set_input_state(self, input_state: BasicState): + super().set_input_state(input_state) + if self._fock_space is None or self._fock_space.m != input_state.m or self._fock_space.n != input_state.n: + self._fock_space = xq.FSArray(input_state.m, input_state.n) + + def _init_mask(self): + super()._init_mask() + # self._slos.set_mask(self._mask) + + def prob_amplitude(self, output_state: FockState) -> complex: + self._slos.set_input_state(self._input_state) + all_pa = self._slos.all_amplitudes() + return all_pa[self._fock_space.find(output_state)] + # if self._mask: + # return all_pa[xq.FSArray(self._input_state.m, self._input_state.n, self._mask).find(output_state)] + # else: + # return all_pa[xq.FSArray(self._input_state.m, self._input_state.n).find(output_state)] + + def prob_distribution(self) -> BSDistribution: + self._slos.set_input_state(self._input_state) + if self._mask is None: + return self._slos.distribution() + + res = BSDistribution() + all_probs = self._slos.all_probabilities() + for p, fs in zip(all_probs, self._fock_space): + if self._mask.match(fs): + res.add(fs, p) + return res + + # return self._slos.distribution() + + def all_prob_ampli(self): + self._slos.set_input_state(self._input_state) + return self._slos.all_amplitudes() + + @property + def name(self) -> str: + return "SLOS_CPP" + + def all_prob(self, input_state: FockState = None): + self._slos.set_input_state(input_state or self._input_state) + return self._slos.all_probabilities() + + def evolve(self) -> StateVector: + self._slos.set_input_state(self._input_state) + all_pa = self._slos.all_amplitudes() + res = StateVector() + for output_state, pa in zip(self._fock_space, all_pa): + # Utterly non-optimized. Mask management should be added in the computation + if self._mask is None or self._mask.match(output_state): + res += output_state * pa + # for output_state, pa in zip(self._get_iterator(self._input_state), all_pa): + # res += output_state * pa + return res diff --git a/perceval/backends/_slos_v2.py b/perceval/backends/_slos_v2.py new file mode 100644 index 00000000..33ddd9ee --- /dev/null +++ b/perceval/backends/_slos_v2.py @@ -0,0 +1,104 @@ +# MIT License +# +# Copyright (c) 2022 Quandela +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# As a special exception, the copyright holders of exqalibur library give you +# permission to combine exqalibur with code included in the standard release of +# Perceval under the MIT license (or modified versions of such code). You may +# copy and distribute such a combined system following the terms of the MIT +# license for both exqalibur and Perceval. This exception for the usage of +# exqalibur is limited to the python bindings used by Perceval. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# 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. +import exqalibur as xq +from perceval.utils import FockState, BSDistribution, StateVector +from perceval.components import ACircuit +from perceval.utils.states import BasicState + +from ._abstract_backends import AStrongSimulationBackend + + +class SLOSV2Backend(AStrongSimulationBackend): + + def __init__(self, mask=None): + super().__init__() + self._slos = xq.SLOS_V2() + self._fock_space = None + if mask: + self.set_mask(mask) + + def set_circuit(self, circuit: ACircuit): + super().set_circuit(circuit) # Computes circuit unitary as _umat + self._slos.set_unitary(self._umat) + + def set_input_state(self, input_state: BasicState): + super().set_input_state(input_state) + if self._fock_space is None or self._fock_space.m != input_state.m or self._fock_space.n != input_state.n: + self._fock_space = xq.FSArray(input_state.m, input_state.n) + + def _init_mask(self): + super()._init_mask() + self._slos.set_mask(self._mask) + + def prob_amplitude(self, output_state: FockState) -> complex: + self._slos.set_input_state(self._input_state) + all_pa = self._slos.all_amplitudes() + return all_pa[self._fock_space.find(output_state)] + # if self._mask: + # return all_pa[xq.FSArray(self._input_state.m, self._input_state.n, self._mask).find(output_state)] + # else: + # return all_pa[xq.FSArray(self._input_state.m, self._input_state.n).find(output_state)] + + def prob_distribution(self) -> BSDistribution: + self._slos.set_input_state(self._input_state) + if self._mask is None: + return self._slos.distribution() + + res = BSDistribution() + all_probs = self._slos.all_probabilities() + for p, fs in zip(all_probs, self._fock_space): + if self._mask.match(fs): + res.add(fs, p) + return res + + # return self._slos.distribution() + + def all_prob_ampli(self): + self._slos.set_input_state(self._input_state) + return self._slos.all_amplitudes() + + @property + def name(self) -> str: + return "SLOS_V2" + + def all_prob(self, input_state: FockState = None): + self._slos.set_input_state(input_state or self._input_state) + return self._slos.all_probabilities() + + def evolve(self) -> StateVector: + self._slos.set_input_state(self._input_state) + all_pa = self._slos.all_amplitudes() + res = StateVector() + for output_state, pa in zip(self._fock_space, all_pa): + # Utterly non-optimized. Mask management should be added in the computation + if self._mask is None or self._mask.match(output_state): + res += output_state * pa + # for output_state, pa in zip(self._get_iterator(self._input_state), all_pa): + # res += output_state * pa + return res diff --git a/tests/backends/test_backends.py b/tests/backends/test_backends.py index 0447373a..eabe73b2 100644 --- a/tests/backends/test_backends.py +++ b/tests/backends/test_backends.py @@ -88,13 +88,13 @@ def test_backend_factory_default(): {BasicState("|1,0>"): 0.5, BasicState("|0,1>"): 0.5}) -@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "NaiveApprox", "SLAP"]) +@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "NaiveApprox", "SLAP", "SLOS_CPP", "SLOS_V2"]) def test_backend_wiring(backend_name): backend: AStrongSimulationBackend = BackendFactory.get_backend(backend_name) backend.set_circuit(Circuit(1)) # Identity circuit, 1 mode check_output_distribution(backend, BasicState([1]), {BasicState("|1>"): 1}) -@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP"]) +@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP", "SLOS_CPP", "SLOS_V2"]) def test_backend_identity(backend_name): backend: AStrongSimulationBackend = BackendFactory.get_backend(backend_name) backend.set_circuit(Circuit(2)) # Identity circuit, 2 modes @@ -103,7 +103,7 @@ def test_backend_identity(backend_name): check_output_distribution(backend, BasicState([1, 1]), {BasicState("|1,1>"): 1}) -@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "CliffordClifford2017", "SLAP"]) +@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "CliffordClifford2017", "SLAP", "SLOS_CPP", "SLOS_V2"]) def test_backend_wrong_size(backend_name): circuit = Circuit(2) state = BasicState([1, 1, 1]) @@ -113,7 +113,7 @@ def test_backend_wrong_size(backend_name): backend.set_input_state(state) -@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP"]) +@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP", "SLOS_CPP", "SLOS_V2"]) def test_backend_sym_bs(backend_name): backend: AStrongSimulationBackend = BackendFactory.get_backend(backend_name) backend.set_circuit(BS.H()) @@ -129,7 +129,7 @@ def test_backend_sym_bs(backend_name): BasicState("|0,2>"): 0.5}) -@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP"]) +@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP", "SLOS_CPP", "SLOS_V2"]) def test_backend_asym_bs(backend_name): backend: AStrongSimulationBackend = BackendFactory.get_backend(backend_name) backend.set_circuit(BS.H(theta=2*math.pi/3)) @@ -167,7 +167,7 @@ def test_slos_symbolic(): assert str(slos.probability(BasicState([0, 1]))) == "1.0*sin(theta/2)**2" -@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP"]) +@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP", "SLOS_CPP", "SLOS_V2"]) def test_backend_cnot(backend_name): if backend_name == "MPS": # For MPS to be accurate enough, we need to increase the cutoff @@ -185,7 +185,7 @@ def test_backend_cnot(backend_name): assert pytest.approx(non_post_selected_probability) == 7/9 -@pytest.mark.parametrize("backend_name", ["SLOS", "SLAP"]) +@pytest.mark.parametrize("backend_name", ["SLOS", "SLAP", "SLOS_CPP", "SLOS_V2"]) def test_cnot_with_mask(backend_name): backend = BackendFactory.get_backend(backend_name) backend.set_mask([" 00"]) @@ -200,7 +200,7 @@ def test_cnot_with_mask(backend_name): assert pytest.approx(non_post_selected_probability) == 0 -@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP"]) +@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP", "SLOS_CPP", "SLOS_V2"]) def test_strong_sim_with_mask(backend_name): if backend_name == "MPS": # For MPS to be accurate enough, we need to increase the cutoff @@ -218,7 +218,7 @@ def test_strong_sim_with_mask(backend_name): assert bsd[BasicState([1, 1, 0, 0, 0, 0])] == pytest.approx(1 / 9) -@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP"]) +@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP", "SLOS_CPP", "SLOS_V2"]) def test_probampli_backends(backend_name): backend: AStrongSimulationBackend = BackendFactory.get_backend(backend_name) circuit = Circuit(3) // BS.H() // (1, PS(math.pi/4)) // (1, BS.H()) @@ -277,7 +277,7 @@ def test_slos_refresh_coefs(): }) -@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP"]) +@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP", "SLOS_CPP", "SLOS_V2"]) def test_evolve_indistinguishable(backend_name): backend: AStrongSimulationBackend = BackendFactory.get_backend(backend_name) backend.set_circuit(BS.H()) From 58af7ca31e3837d93bf97021fcca9115e0602965 Mon Sep 17 00:00:00 2001 From: Benoit Fanchon Date: Tue, 16 Dec 2025 19:27:28 +0100 Subject: [PATCH 02/11] Benchmark --- Bench_backends.py | 83 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 Bench_backends.py diff --git a/Bench_backends.py b/Bench_backends.py new file mode 100644 index 00000000..4403e780 --- /dev/null +++ b/Bench_backends.py @@ -0,0 +1,83 @@ +import gc +import time +import perceval as pcvl +import psutil +import exqalibur + +def human_readable_size(size, decimal_places=2): + for unit in ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB']: + if size < 1024.0 or unit == 'PiB': + break + size /= 1024.0 + return f"{size:.{decimal_places}f} {unit}" + +def bench(m, n, backends): + u1 = pcvl.Matrix.random_unitary(m) + photons = [ 0 ] * m + photons[0] = n + bs = pcvl.BasicState(photons) + circuit = pcvl.Circuit(m) // pcvl.components.Unitary(u1) + + result = [] + for backend_name in backends: + if backend_name == "SLAP" and n > 15: + print("\t-- ", end='', flush=True) + continue + if backend_name == "SLOS" and n > 18: + print("\t-- ", end='', flush=True) + continue + backend = pcvl.backends.BackendFactory.get_backend(backend_name) + backend.set_circuit(circuit) + backend.set_input_state(bs) + start = time.time() + bsd = backend.prob_distribution() + end = time.time() + print(f"\t{end-start}", end='', flush=True) + result.append(end - start) + + return result + + +def bench_mem(m, n, backends): + u1 = pcvl.Matrix.random_unitary(m) + photons = [ 0 ] * m + photons[0] = n + bs = pcvl.BasicState(photons) + circuit = pcvl.Circuit(m) // pcvl.components.Unitary(u1) + + for backend_name in backends: + # if backend_name == "SLOS" and n > 12: + # break + backend = pcvl.backends.BackendFactory.get_backend(backend_name) + backend.set_circuit(circuit) + backend.set_input_state(bs) + gc.collect() + mem_1 = psutil.Process().memory_info().rss + bsd = backend.prob_distribution() + mem_2 = psutil.Process().memory_info().rss + print(f"{backend_name}\t{mem_1}\t{mem_2}\t{human_readable_size(mem_2-mem_1)}") + +if __name__ == "__main__": + import argparse + parser = argparse.ArgumentParser(description='Benchmarks of strong simulation backends') + parser.add_argument('--modes', '-m', + type=int, required=True, action='store', + help='number of modes') + parser.add_argument('--nphotons', '-n', + type=int, action='store', default=20, + help='number of modes') + parser.add_argument('--minphotons', '-i', + type=int, action='store', default=4, + help='number of modes') + parser.add_argument('--memusage', '-u', action='store_true', default=False) + args = parser.parse_args() + backends = [ "SLOS", "SLOS_CPP", "SLAP", "SLOS_V2" ] + + if args.memusage: + bench_mem(args.modes, args.nphotons, backends) + else: + print('\t', "\t".join(backends)) + for n in range(args.minphotons, args.nphotons + 1): + print(f"{n}", end='') + bench(args.modes, n, backends) + print("") From f2b1e6e5ac3076d5612dee94740b54bdf0a2dbcb Mon Sep 17 00:00:00 2001 From: Benoit Fanchon Date: Tue, 27 Jan 2026 16:53:13 +0100 Subject: [PATCH 03/11] SLOS backends --- perceval/backends/__init__.py | 4 +- perceval/backends/_slos.py | 4 +- perceval/backends/_slos_cpp.py | 22 +++---- perceval/backends/_slos_v2.py | 33 +++++++---- perceval/backends/_slos_v3.py | 102 ++++++++++++++++++++++++++++++++ tests/backends/test_backends.py | 22 +++---- 6 files changed, 151 insertions(+), 36 deletions(-) create mode 100644 perceval/backends/_slos_v3.py diff --git a/perceval/backends/__init__.py b/perceval/backends/__init__.py index ee88429a..bc521608 100644 --- a/perceval/backends/__init__.py +++ b/perceval/backends/__init__.py @@ -35,6 +35,7 @@ from ._slos import SLOSBackend from ._slap import SLAPBackend from ._slos_v2 import SLOSV2Backend +from ._slos_v3 import SLOSV3Backend from ._slos_cpp import SLOSCPPBackend @@ -46,7 +47,8 @@ "SLAP": SLAPBackend, "SLOS": SLOSBackend, "SLOS_CPP": SLOSCPPBackend, - "SLOS_V2": SLOSV2Backend + "SLOS_V2": SLOSV2Backend, + "SLOS_V3": SLOSV3Backend } diff --git a/perceval/backends/_slos.py b/perceval/backends/_slos.py index aeb01e7a..33a22765 100644 --- a/perceval/backends/_slos.py +++ b/perceval/backends/_slos.py @@ -158,10 +158,10 @@ def _deploy(self, input_list: list[FockState]): n = input_state.n if n < len(self._fsms) and n not in self._fsas: # we are missing the intermediate states - let us retrieve/load it back - current_fsa = xq.FSArray(m, n, self._mask) if self._mask else xq.FSArray(m, n) + current_fsa = xq.FSArray(m, n, self._mask) # if self._mask else xq.FSArray(m, n) for k in range(len(self._fsms), n + 1): fsa_n_m1 = current_fsa - current_fsa = xq.FSArray(m, k, self._mask) if self._mask else xq.FSArray(m, k) + current_fsa = xq.FSArray(m, k, self._mask) # if self._mask else xq.FSArray(m, k) self._mk_l.append(current_fsa.count()) self._fsms.append(xq.FSMap(current_fsa, fsa_n_m1, True)) if n not in self._fsas: diff --git a/perceval/backends/_slos_cpp.py b/perceval/backends/_slos_cpp.py index ccaeea85..fbd65ba3 100644 --- a/perceval/backends/_slos_cpp.py +++ b/perceval/backends/_slos_cpp.py @@ -50,11 +50,11 @@ def set_circuit(self, circuit: ACircuit): def set_input_state(self, input_state: BasicState): super().set_input_state(input_state) if self._fock_space is None or self._fock_space.m != input_state.m or self._fock_space.n != input_state.n: - self._fock_space = xq.FSArray(input_state.m, input_state.n) + self._fock_space = xq.FSArray(input_state.m, input_state.n, self._mask) def _init_mask(self): super()._init_mask() - # self._slos.set_mask(self._mask) + self._slos.set_mask(self._mask) def prob_amplitude(self, output_state: FockState) -> complex: self._slos.set_input_state(self._input_state) @@ -67,17 +67,17 @@ def prob_amplitude(self, output_state: FockState) -> complex: def prob_distribution(self) -> BSDistribution: self._slos.set_input_state(self._input_state) - if self._mask is None: - return self._slos.distribution() + # if self._mask is None: + # return self._slos.distribution() - res = BSDistribution() - all_probs = self._slos.all_probabilities() - for p, fs in zip(all_probs, self._fock_space): - if self._mask.match(fs): - res.add(fs, p) - return res + # res = BSDistribution() + # all_probs = self._slos.all_probabilities() + # for p, fs in zip(all_probs, self._fock_space): + # if self._mask.match(fs): + # res.add(fs, p) + # return res - # return self._slos.distribution() + return self._slos.distribution() def all_prob_ampli(self): self._slos.set_input_state(self._input_state) diff --git a/perceval/backends/_slos_v2.py b/perceval/backends/_slos_v2.py index 33ddd9ee..28f0ce82 100644 --- a/perceval/backends/_slos_v2.py +++ b/perceval/backends/_slos_v2.py @@ -29,6 +29,7 @@ import exqalibur as xq from perceval.utils import FockState, BSDistribution, StateVector from perceval.components import ACircuit +from perceval.utils.postselect import PostSelect from perceval.utils.states import BasicState from ._abstract_backends import AStrongSimulationBackend @@ -42,6 +43,7 @@ def __init__(self, mask=None): self._fock_space = None if mask: self.set_mask(mask) + self._post_select = None def set_circuit(self, circuit: ACircuit): super().set_circuit(circuit) # Computes circuit unitary as _umat @@ -50,13 +52,17 @@ def set_circuit(self, circuit: ACircuit): def set_input_state(self, input_state: BasicState): super().set_input_state(input_state) if self._fock_space is None or self._fock_space.m != input_state.m or self._fock_space.n != input_state.n: - self._fock_space = xq.FSArray(input_state.m, input_state.n) + self._fock_space = xq.FSArray(input_state.m, input_state.n, self._mask) def _init_mask(self): super()._init_mask() self._slos.set_mask(self._mask) + def set_post_select(self, post_selection: PostSelect): + self._post_select = post_selection + def prob_amplitude(self, output_state: FockState) -> complex: + self._slos.set_post_select(self._post_select) self._slos.set_input_state(self._input_state) all_pa = self._slos.all_amplitudes() return all_pa[self._fock_space.find(output_state)] @@ -66,20 +72,22 @@ def prob_amplitude(self, output_state: FockState) -> complex: # return all_pa[xq.FSArray(self._input_state.m, self._input_state.n).find(output_state)] def prob_distribution(self) -> BSDistribution: + self._slos.set_post_select(self._post_select) self._slos.set_input_state(self._input_state) - if self._mask is None: - return self._slos.distribution() - - res = BSDistribution() - all_probs = self._slos.all_probabilities() - for p, fs in zip(all_probs, self._fock_space): - if self._mask.match(fs): - res.add(fs, p) - return res + # if self._mask is None: + # return self._slos.distribution() - # return self._slos.distribution() + # res = BSDistribution() + # all_probs = self._slos.all_probabilities() + # for p, fs in zip(all_probs, self._fock_space): + # if self._mask.match(fs): + # res.add(fs, p) + # return res + + return self._slos.distribution() def all_prob_ampli(self): + self._slos.set_post_select(self._post_select) self._slos.set_input_state(self._input_state) return self._slos.all_amplitudes() @@ -102,3 +110,6 @@ def evolve(self) -> StateVector: # for output_state, pa in zip(self._get_iterator(self._input_state), all_pa): # res += output_state * pa return res + + def reset(self): + self._slos.reset() diff --git a/perceval/backends/_slos_v3.py b/perceval/backends/_slos_v3.py new file mode 100644 index 00000000..566e4728 --- /dev/null +++ b/perceval/backends/_slos_v3.py @@ -0,0 +1,102 @@ +# MIT License +# +# Copyright (c) 2022 Quandela +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# As a special exception, the copyright holders of exqalibur library give you +# permission to combine exqalibur with code included in the standard release of +# Perceval under the MIT license (or modified versions of such code). You may +# copy and distribute such a combined system following the terms of the MIT +# license for both exqalibur and Perceval. This exception for the usage of +# exqalibur is limited to the python bindings used by Perceval. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# 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. +import exqalibur as xq +from perceval.utils import FockState, BSDistribution, StateVector +from perceval.components import ACircuit +from perceval.utils.postselect import PostSelect +from perceval.utils.states import BasicState + +from ._abstract_backends import AStrongSimulationBackend + + +class SLOSV3Backend(AStrongSimulationBackend): + + def __init__(self, mask=None): + super().__init__() + self._slos = xq.SLOS_V3() + self._fock_space = None + if mask: + self.set_mask(mask) + self._post_select = None + + def set_circuit(self, circuit: ACircuit): + super().set_circuit(circuit) # Computes circuit unitary as _umat + self._slos.set_unitary(self._umat) + + def set_input_state(self, input_state: BasicState): + super().set_input_state(input_state) + if self._fock_space is None or self._fock_space.m != input_state.m or self._fock_space.n != input_state.n: + self._fock_space = xq.FSArray(input_state.m, input_state.n, self._mask) + + def _init_mask(self): + super()._init_mask() + self._slos.set_mask(self._mask) + + def set_post_select(self, post_selection: PostSelect): + self._post_select = post_selection + + def prob_amplitude(self, output_state: FockState) -> complex: + self._slos.set_post_select(self._post_select) + self._slos.set_input_state(self._input_state) + all_pa = self._slos.all_amplitudes() + return all_pa[self._fock_space.find(output_state)] + + def prob_distribution(self) -> BSDistribution: + self._slos.set_post_select(self._post_select) + self._slos.set_input_state(self._input_state) + + return self._slos.distribution() + + def all_prob_ampli(self): + self._slos.set_post_select(self._post_select) + self._slos.set_input_state(self._input_state) + return self._slos.all_amplitudes() + + @property + def name(self) -> str: + return "SLOS_V3" + + def all_prob(self, input_state: FockState = None): + self._slos.set_input_state(input_state or self._input_state) + return self._slos.all_probabilities() + + def evolve(self) -> StateVector: + self._slos.set_input_state(self._input_state) + all_pa = self._slos.all_amplitudes() + res = StateVector() + for output_state, pa in zip(self._fock_space, all_pa): + # Utterly non-optimized. Mask management should be added in the computation + if self._mask is None or self._mask.match(output_state): + res += output_state * pa + # for output_state, pa in zip(self._get_iterator(self._input_state), all_pa): + # res += output_state * pa + return res + + def reset(self): + self._slos.reset() diff --git a/tests/backends/test_backends.py b/tests/backends/test_backends.py index eabe73b2..37bce97d 100644 --- a/tests/backends/test_backends.py +++ b/tests/backends/test_backends.py @@ -88,13 +88,13 @@ def test_backend_factory_default(): {BasicState("|1,0>"): 0.5, BasicState("|0,1>"): 0.5}) -@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "NaiveApprox", "SLAP", "SLOS_CPP", "SLOS_V2"]) +@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "NaiveApprox", "SLAP", "SLOS_CPP", "SLOS_V2", "SLOS_V3"]) def test_backend_wiring(backend_name): backend: AStrongSimulationBackend = BackendFactory.get_backend(backend_name) backend.set_circuit(Circuit(1)) # Identity circuit, 1 mode check_output_distribution(backend, BasicState([1]), {BasicState("|1>"): 1}) -@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP", "SLOS_CPP", "SLOS_V2"]) +@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP", "SLOS_CPP", "SLOS_V2", "SLOS_V3"]) def test_backend_identity(backend_name): backend: AStrongSimulationBackend = BackendFactory.get_backend(backend_name) backend.set_circuit(Circuit(2)) # Identity circuit, 2 modes @@ -103,7 +103,7 @@ def test_backend_identity(backend_name): check_output_distribution(backend, BasicState([1, 1]), {BasicState("|1,1>"): 1}) -@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "CliffordClifford2017", "SLAP", "SLOS_CPP", "SLOS_V2"]) +@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "CliffordClifford2017", "SLAP", "SLOS_CPP", "SLOS_V2", "SLOS_V3"]) def test_backend_wrong_size(backend_name): circuit = Circuit(2) state = BasicState([1, 1, 1]) @@ -113,7 +113,7 @@ def test_backend_wrong_size(backend_name): backend.set_input_state(state) -@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP", "SLOS_CPP", "SLOS_V2"]) +@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP", "SLOS_CPP", "SLOS_V2", "SLOS_V3"]) def test_backend_sym_bs(backend_name): backend: AStrongSimulationBackend = BackendFactory.get_backend(backend_name) backend.set_circuit(BS.H()) @@ -129,7 +129,7 @@ def test_backend_sym_bs(backend_name): BasicState("|0,2>"): 0.5}) -@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP", "SLOS_CPP", "SLOS_V2"]) +@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP", "SLOS_CPP", "SLOS_V2", "SLOS_V3"]) def test_backend_asym_bs(backend_name): backend: AStrongSimulationBackend = BackendFactory.get_backend(backend_name) backend.set_circuit(BS.H(theta=2*math.pi/3)) @@ -167,7 +167,7 @@ def test_slos_symbolic(): assert str(slos.probability(BasicState([0, 1]))) == "1.0*sin(theta/2)**2" -@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP", "SLOS_CPP", "SLOS_V2"]) +@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP", "SLOS_CPP", "SLOS_V2", "SLOS_V3"]) def test_backend_cnot(backend_name): if backend_name == "MPS": # For MPS to be accurate enough, we need to increase the cutoff @@ -185,13 +185,13 @@ def test_backend_cnot(backend_name): assert pytest.approx(non_post_selected_probability) == 7/9 -@pytest.mark.parametrize("backend_name", ["SLOS", "SLAP", "SLOS_CPP", "SLOS_V2"]) +@pytest.mark.parametrize("backend_name", ["SLOS", "SLAP", "SLOS_CPP", "SLOS_V2", "SLOS_V3"]) def test_cnot_with_mask(backend_name): backend = BackendFactory.get_backend(backend_name) backend.set_mask([" 00"]) cnot = catalog["postprocessed cnot"].build_circuit() backend.set_circuit(cnot) - _assert_cnot(backend) + _assert_cnot(backend, backend_name) non_post_selected_probability = 0 backend.set_input_state(BasicState([0, 1, 0, 1, 0, 0])) for output_state, prob in backend.prob_distribution().items(): @@ -200,7 +200,7 @@ def test_cnot_with_mask(backend_name): assert pytest.approx(non_post_selected_probability) == 0 -@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP", "SLOS_CPP", "SLOS_V2"]) +@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP", "SLOS_CPP", "SLOS_V2", "SLOS_V3"]) def test_strong_sim_with_mask(backend_name): if backend_name == "MPS": # For MPS to be accurate enough, we need to increase the cutoff @@ -218,7 +218,7 @@ def test_strong_sim_with_mask(backend_name): assert bsd[BasicState([1, 1, 0, 0, 0, 0])] == pytest.approx(1 / 9) -@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP", "SLOS_CPP", "SLOS_V2"]) +@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP", "SLOS_CPP", "SLOS_V2", "SLOS_V3"]) def test_probampli_backends(backend_name): backend: AStrongSimulationBackend = BackendFactory.get_backend(backend_name) circuit = Circuit(3) // BS.H() // (1, PS(math.pi/4)) // (1, BS.H()) @@ -277,7 +277,7 @@ def test_slos_refresh_coefs(): }) -@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP", "SLOS_CPP", "SLOS_V2"]) +@pytest.mark.parametrize("backend_name", ["SLOS", "Naive", "MPS", "SLAP", "SLOS_CPP", "SLOS_V2", "SLOS_V3"]) def test_evolve_indistinguishable(backend_name): backend: AStrongSimulationBackend = BackendFactory.get_backend(backend_name) backend.set_circuit(BS.H()) From 202e464ea1318af65b30d72c3a892f887f3d02d3 Mon Sep 17 00:00:00 2001 From: Benoit Fanchon Date: Thu, 5 Feb 2026 10:19:44 +0100 Subject: [PATCH 04/11] SLOS --- Bench_backends.py | 57 +++++++++++++++++++++++---- perceval/backends/_slos_cpp.py | 15 ------- perceval/backends/_slos_v2.py | 69 +++++++++++++++------------------ perceval/backends/_slos_v3.py | 56 ++++++++++++++------------ tests/backends/test_backends.py | 6 +-- 5 files changed, 114 insertions(+), 89 deletions(-) diff --git a/Bench_backends.py b/Bench_backends.py index 4403e780..0567aa7b 100644 --- a/Bench_backends.py +++ b/Bench_backends.py @@ -2,7 +2,13 @@ import time import perceval as pcvl import psutil -import exqalibur +from perceval.backends import BACKEND_LIST +from perceval.backends._slos_v2 import SLOSV2Backend +from perceval.backends._slos_v3 import SLOSV3Backend +from perceval.utils.postselect import PostSelect +import exqalibur as xq +BACKEND_LIST[ "SLOS_V2_PS" ] = SLOSV2Backend +BACKEND_LIST[ "SLOS_V3_PS" ] = SLOSV3Backend def human_readable_size(size, decimal_places=2): for unit in ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB']: @@ -15,7 +21,17 @@ def bench(m, n, backends): u1 = pcvl.Matrix.random_unitary(m) photons = [ 0 ] * m photons[0] = n + ps = PostSelect('[0]==1 & [1]==1 & [2]==1 & [3]==1') + # ps = PostSelect('[0]==0 & [1]==0 & [2]==0 & [3]==0') + # ps = PostSelect('[0]==1 & [1]==1 & [2]==0 & [3]==0') + mask_str = "1111" + "*" * (m-4) + # mask_str = "0000" + "*" * (m-4) + # mask_str = "1100" + "*" * (m-4) + mask = xq.FSMask(m, n, [mask_str]) bs = pcvl.BasicState(photons) + photons[0] = n - 1 + photons[1] = 1 + bs2 = pcvl.BasicState(photons) circuit = pcvl.Circuit(m) // pcvl.components.Unitary(u1) result = [] @@ -26,13 +42,24 @@ def bench(m, n, backends): if backend_name == "SLOS" and n > 18: print("\t-- ", end='', flush=True) continue + start = time.time() backend = pcvl.backends.BackendFactory.get_backend(backend_name) + if backend_name == "SLOS_V2_PS" or backend_name == "SLOS_V3_PS": + backend.set_post_select(ps) + pass + else: + backend.set_mask(mask_str, n) + pass backend.set_circuit(circuit) backend.set_input_state(bs) - start = time.time() - bsd = backend.prob_distribution() + bsd = backend.all_prob() end = time.time() - print(f"\t{end-start}", end='', flush=True) + + backend.set_input_state(bs2) + bsd = backend.all_prob() + # bsd = backend.prob_distribution() + end2 = time.time() + print(f"\t{end-start:.4f} {end2-end:.4f}", end='', flush=True) result.append(end - start) return result @@ -48,13 +75,16 @@ def bench_mem(m, n, backends): for backend_name in backends: # if backend_name == "SLOS" and n > 12: # break + gc.collect() + mem_1 = psutil.Process().memory_info().rss backend = pcvl.backends.BackendFactory.get_backend(backend_name) backend.set_circuit(circuit) backend.set_input_state(bs) - gc.collect() - mem_1 = psutil.Process().memory_info().rss bsd = backend.prob_distribution() mem_2 = psutil.Process().memory_info().rss + backend = None + bsd = None + gc.collect() print(f"{backend_name}\t{mem_1}\t{mem_2}\t{human_readable_size(mem_2-mem_1)}") if __name__ == "__main__": @@ -70,11 +100,22 @@ def bench_mem(m, n, backends): type=int, action='store', default=4, help='number of modes') parser.add_argument('--memusage', '-u', action='store_true', default=False) + parser.add_argument('--backend', '-b', + type=str, action='store', default='SLOS', + help='backend used') args = parser.parse_args() - backends = [ "SLOS", "SLOS_CPP", "SLAP", "SLOS_V2" ] + backends = [ + "SLOS", + "SLAP", + "SLOS_CPP", + "SLOS_V2", + "SLOS_V2_PS", + "SLOS_V3", + "SLOS_V3_PS", + ] if args.memusage: - bench_mem(args.modes, args.nphotons, backends) + bench_mem(args.modes, args.nphotons, [args.backend]) else: print('\t', "\t".join(backends)) for n in range(args.minphotons, args.nphotons + 1): diff --git a/perceval/backends/_slos_cpp.py b/perceval/backends/_slos_cpp.py index fbd65ba3..571f01de 100644 --- a/perceval/backends/_slos_cpp.py +++ b/perceval/backends/_slos_cpp.py @@ -60,22 +60,9 @@ def prob_amplitude(self, output_state: FockState) -> complex: self._slos.set_input_state(self._input_state) all_pa = self._slos.all_amplitudes() return all_pa[self._fock_space.find(output_state)] - # if self._mask: - # return all_pa[xq.FSArray(self._input_state.m, self._input_state.n, self._mask).find(output_state)] - # else: - # return all_pa[xq.FSArray(self._input_state.m, self._input_state.n).find(output_state)] def prob_distribution(self) -> BSDistribution: self._slos.set_input_state(self._input_state) - # if self._mask is None: - # return self._slos.distribution() - - # res = BSDistribution() - # all_probs = self._slos.all_probabilities() - # for p, fs in zip(all_probs, self._fock_space): - # if self._mask.match(fs): - # res.add(fs, p) - # return res return self._slos.distribution() @@ -99,6 +86,4 @@ def evolve(self) -> StateVector: # Utterly non-optimized. Mask management should be added in the computation if self._mask is None or self._mask.match(output_state): res += output_state * pa - # for output_state, pa in zip(self._get_iterator(self._input_state), all_pa): - # res += output_state * pa return res diff --git a/perceval/backends/_slos_v2.py b/perceval/backends/_slos_v2.py index 28f0ce82..0d388ff4 100644 --- a/perceval/backends/_slos_v2.py +++ b/perceval/backends/_slos_v2.py @@ -32,15 +32,14 @@ from perceval.utils.postselect import PostSelect from perceval.utils.states import BasicState -from ._abstract_backends import AStrongSimulationBackend +from ._abstract_backends import ABackend -class SLOSV2Backend(AStrongSimulationBackend): +class SLOSV2Backend(ABackend): def __init__(self, mask=None): super().__init__() self._slos = xq.SLOS_V2() - self._fock_space = None if mask: self.set_mask(mask) self._post_select = None @@ -48,47 +47,49 @@ def __init__(self, mask=None): def set_circuit(self, circuit: ACircuit): super().set_circuit(circuit) # Computes circuit unitary as _umat self._slos.set_unitary(self._umat) + if self._circuit and self._input_state: + assert self._circuit.m == self._input_state.m, f'Circuit({self._circuit.m}) and state({self._input_state}) size mismatch' def set_input_state(self, input_state: BasicState): - super().set_input_state(input_state) - if self._fock_space is None or self._fock_space.m != input_state.m or self._fock_space.n != input_state.n: - self._fock_space = xq.FSArray(input_state.m, input_state.n, self._mask) + self._input_state = input_state + self._slos.set_input_state(input_state) + if self._circuit and self._input_state: + assert self._circuit.m == self._input_state.m, f'Circuit({self._circuit.m}) and state({self._input_state}) size mismatch' def _init_mask(self): super()._init_mask() self._slos.set_mask(self._mask) + def set_mask(self, masks: str | list[str], n = None, at_least_modes = None): + if isinstance(masks, str): + masks = [masks] + mask_length = len(masks[0]) + for mask in masks: + mask = mask.replace("*", " ") + assert len(mask) == mask_length, "Inconsistent mask lengths" + fsmask = None + if masks is not None: + if at_least_modes: + fsmask = xq.FSMask(mask_length, n or self._input_state.n, masks, at_least_modes) + else: + fsmask = xq.FSMask(mask_length, n or self._input_state.n, masks) + self._slos.set_mask(fsmask) + def set_post_select(self, post_selection: PostSelect): - self._post_select = post_selection + self._slos.set_post_select(post_selection) def prob_amplitude(self, output_state: FockState) -> complex: - self._slos.set_post_select(self._post_select) - self._slos.set_input_state(self._input_state) all_pa = self._slos.all_amplitudes() - return all_pa[self._fock_space.find(output_state)] - # if self._mask: - # return all_pa[xq.FSArray(self._input_state.m, self._input_state.n, self._mask).find(output_state)] - # else: - # return all_pa[xq.FSArray(self._input_state.m, self._input_state.n).find(output_state)] - - def prob_distribution(self) -> BSDistribution: - self._slos.set_post_select(self._post_select) - self._slos.set_input_state(self._input_state) - # if self._mask is None: - # return self._slos.distribution() + idx = self._slos.get_index(output_state) + return all_pa[idx] - # res = BSDistribution() - # all_probs = self._slos.all_probabilities() - # for p, fs in zip(all_probs, self._fock_space): - # if self._mask.match(fs): - # res.add(fs, p) - # return res + def probability(self, output_state: FockState) -> float: + return abs(self.prob_amplitude(output_state)) ** 2 + def prob_distribution(self) -> BSDistribution: return self._slos.distribution() def all_prob_ampli(self): - self._slos.set_post_select(self._post_select) - self._slos.set_input_state(self._input_state) return self._slos.all_amplitudes() @property @@ -96,20 +97,12 @@ def name(self) -> str: return "SLOS_V2" def all_prob(self, input_state: FockState = None): - self._slos.set_input_state(input_state or self._input_state) return self._slos.all_probabilities() def evolve(self) -> StateVector: self._slos.set_input_state(self._input_state) all_pa = self._slos.all_amplitudes() res = StateVector() - for output_state, pa in zip(self._fock_space, all_pa): - # Utterly non-optimized. Mask management should be added in the computation - if self._mask is None or self._mask.match(output_state): - res += output_state * pa - # for output_state, pa in zip(self._get_iterator(self._input_state), all_pa): - # res += output_state * pa + for output_state, pa in zip(self._slos.get_fockspace(), all_pa): + res += output_state * pa return res - - def reset(self): - self._slos.reset() diff --git a/perceval/backends/_slos_v3.py b/perceval/backends/_slos_v3.py index 566e4728..0f29cabe 100644 --- a/perceval/backends/_slos_v3.py +++ b/perceval/backends/_slos_v3.py @@ -32,15 +32,14 @@ from perceval.utils.postselect import PostSelect from perceval.utils.states import BasicState -from ._abstract_backends import AStrongSimulationBackend +from ._abstract_backends import ABackend -class SLOSV3Backend(AStrongSimulationBackend): +class SLOSV3Backend(ABackend): def __init__(self, mask=None): super().__init__() self._slos = xq.SLOS_V3() - self._fock_space = None if mask: self.set_mask(mask) self._post_select = None @@ -48,34 +47,49 @@ def __init__(self, mask=None): def set_circuit(self, circuit: ACircuit): super().set_circuit(circuit) # Computes circuit unitary as _umat self._slos.set_unitary(self._umat) + if self._circuit and self._input_state: + assert self._circuit.m == self._input_state.m, f'Circuit({self._circuit.m}) and state({self._input_state}) size mismatch' def set_input_state(self, input_state: BasicState): - super().set_input_state(input_state) - if self._fock_space is None or self._fock_space.m != input_state.m or self._fock_space.n != input_state.n: - self._fock_space = xq.FSArray(input_state.m, input_state.n, self._mask) + self._input_state = input_state + self._slos.set_input_state(input_state) + if self._circuit and self._input_state: + assert self._circuit.m == self._input_state.m, f'Circuit({self._circuit.m}) and state({self._input_state}) size mismatch' def _init_mask(self): super()._init_mask() self._slos.set_mask(self._mask) + def set_mask(self, masks: str | list[str], n = None, at_least_modes = None): + if isinstance(masks, str): + masks = [masks] + mask_length = len(masks[0]) + for mask in masks: + mask = mask.replace("*", " ") + assert len(mask) == mask_length, "Inconsistent mask lengths" + fsmask = None + if masks is not None: + if at_least_modes: + fsmask = xq.FSMask(mask_length, n or self._input_state.n, masks, at_least_modes) + else: + fsmask = xq.FSMask(mask_length, n or self._input_state.n, masks) + self._slos.set_mask(fsmask) + def set_post_select(self, post_selection: PostSelect): - self._post_select = post_selection + self._slos.set_post_select(post_selection) def prob_amplitude(self, output_state: FockState) -> complex: - self._slos.set_post_select(self._post_select) - self._slos.set_input_state(self._input_state) all_pa = self._slos.all_amplitudes() - return all_pa[self._fock_space.find(output_state)] + idx = self._slos.get_index(output_state) + return all_pa[idx] - def prob_distribution(self) -> BSDistribution: - self._slos.set_post_select(self._post_select) - self._slos.set_input_state(self._input_state) + def probability(self, output_state: FockState) -> float: + return abs(self.prob_amplitude(output_state)) ** 2 + def prob_distribution(self) -> BSDistribution: return self._slos.distribution() def all_prob_ampli(self): - self._slos.set_post_select(self._post_select) - self._slos.set_input_state(self._input_state) return self._slos.all_amplitudes() @property @@ -83,20 +97,12 @@ def name(self) -> str: return "SLOS_V3" def all_prob(self, input_state: FockState = None): - self._slos.set_input_state(input_state or self._input_state) return self._slos.all_probabilities() def evolve(self) -> StateVector: self._slos.set_input_state(self._input_state) all_pa = self._slos.all_amplitudes() res = StateVector() - for output_state, pa in zip(self._fock_space, all_pa): - # Utterly non-optimized. Mask management should be added in the computation - if self._mask is None or self._mask.match(output_state): - res += output_state * pa - # for output_state, pa in zip(self._get_iterator(self._input_state), all_pa): - # res += output_state * pa + for output_state, pa in zip(self._slos.get_fockspace(), all_pa): + res += output_state * pa return res - - def reset(self): - self._slos.reset() diff --git a/tests/backends/test_backends.py b/tests/backends/test_backends.py index 37bce97d..aea9cdbc 100644 --- a/tests/backends/test_backends.py +++ b/tests/backends/test_backends.py @@ -188,10 +188,10 @@ def test_backend_cnot(backend_name): @pytest.mark.parametrize("backend_name", ["SLOS", "SLAP", "SLOS_CPP", "SLOS_V2", "SLOS_V3"]) def test_cnot_with_mask(backend_name): backend = BackendFactory.get_backend(backend_name) - backend.set_mask([" 00"]) + backend.set_mask([" 00"], 2) cnot = catalog["postprocessed cnot"].build_circuit() backend.set_circuit(cnot) - _assert_cnot(backend, backend_name) + _assert_cnot(backend) non_post_selected_probability = 0 backend.set_input_state(BasicState([0, 1, 0, 1, 0, 0])) for output_state, prob in backend.prob_distribution().items(): @@ -207,7 +207,7 @@ def test_strong_sim_with_mask(backend_name): backend: AStrongSimulationBackend = BackendFactory.get_backend(backend_name, cutoff=4) else: backend: AStrongSimulationBackend = BackendFactory.get_backend(backend_name) - backend.set_mask("****00") + backend.set_mask("****00", 2) cnot = catalog["postprocessed cnot"].build_circuit() backend.set_circuit(cnot) logical00 = BasicState([1, 0, 1, 0, 0, 0]) From 43c85110246e5a776fcf39f7c82ecdea4d5fb0f0 Mon Sep 17 00:00:00 2001 From: Benoit Fanchon Date: Mon, 9 Feb 2026 19:15:45 +0100 Subject: [PATCH 05/11] xq --- perceval/backends/_slos_v2.py | 2 +- perceval/backends/_slos_v3.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/perceval/backends/_slos_v2.py b/perceval/backends/_slos_v2.py index 0d388ff4..4d5d75e0 100644 --- a/perceval/backends/_slos_v2.py +++ b/perceval/backends/_slos_v2.py @@ -103,6 +103,6 @@ def evolve(self) -> StateVector: self._slos.set_input_state(self._input_state) all_pa = self._slos.all_amplitudes() res = StateVector() - for output_state, pa in zip(self._slos.get_fockspace(), all_pa): + for output_state, pa in zip(self._slos.get_states(), all_pa): res += output_state * pa return res diff --git a/perceval/backends/_slos_v3.py b/perceval/backends/_slos_v3.py index 0f29cabe..1edba559 100644 --- a/perceval/backends/_slos_v3.py +++ b/perceval/backends/_slos_v3.py @@ -103,6 +103,6 @@ def evolve(self) -> StateVector: self._slos.set_input_state(self._input_state) all_pa = self._slos.all_amplitudes() res = StateVector() - for output_state, pa in zip(self._slos.get_fockspace(), all_pa): + for output_state, pa in zip(self._slos.get_states(), all_pa): res += output_state * pa return res From acb10b3d8ebf22cae51490cef4b95e218246768d Mon Sep 17 00:00:00 2001 From: Benoit Fanchon Date: Wed, 11 Feb 2026 15:50:06 +0100 Subject: [PATCH 06/11] benchmarks --- Bench_backends.py | 86 ++++++++++++++++++++++++++++++-------------- benchmarks-memory.sh | 9 +++++ 2 files changed, 68 insertions(+), 27 deletions(-) create mode 100755 benchmarks-memory.sh diff --git a/Bench_backends.py b/Bench_backends.py index 0567aa7b..3b2a12db 100644 --- a/Bench_backends.py +++ b/Bench_backends.py @@ -2,13 +2,20 @@ import time import perceval as pcvl import psutil + from perceval.backends import BACKEND_LIST from perceval.backends._slos_v2 import SLOSV2Backend from perceval.backends._slos_v3 import SLOSV3Backend +from perceval.utils.dist_metrics import tvd_dist from perceval.utils.postselect import PostSelect import exqalibur as xq -BACKEND_LIST[ "SLOS_V2_PS" ] = SLOSV2Backend -BACKEND_LIST[ "SLOS_V3_PS" ] = SLOSV3Backend + +from perceval.utils import get_logger +get_logger().set_level(pcvl.logging.level.warn, pcvl.logging.channel.general) +# get_logger().set_level(pcvl.logging.level.info, pcvl.logging.channel.general) + +# BACKEND_LIST[ "SLOS_V2_PS" ] = SLOSV2Backend +# BACKEND_LIST[ "SLOS_V3_PS" ] = SLOSV3Backend def human_readable_size(size, decimal_places=2): for unit in ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB']: @@ -17,25 +24,35 @@ def human_readable_size(size, decimal_places=2): size /= 1024.0 return f"{size:.{decimal_places}f} {unit}" -def bench(m, n, backends): +def do_compute(backend, method): + if method == 1: + return backend.all_prob_ampli() + if method == 2: + return backend.all_prob() + if method == 3: + return backend.prob_distribution() + +def set_mask(backend, mask_str, m, n): + backend.set_mask((mask_str + "*" * (m-4))[:m], n) + # if backend.name == "SLOS_V2_PS" or backend.name == "SLOS_V3_PS": + # backend.set_post_select(ps) + +def bench(m, n, backends, mask_str, method): u1 = pcvl.Matrix.random_unitary(m) photons = [ 0 ] * m photons[0] = n - ps = PostSelect('[0]==1 & [1]==1 & [2]==1 & [3]==1') - # ps = PostSelect('[0]==0 & [1]==0 & [2]==0 & [3]==0') - # ps = PostSelect('[0]==1 & [1]==1 & [2]==0 & [3]==0') - mask_str = "1111" + "*" * (m-4) - # mask_str = "0000" + "*" * (m-4) - # mask_str = "1100" + "*" * (m-4) - mask = xq.FSMask(m, n, [mask_str]) bs = pcvl.BasicState(photons) photons[0] = n - 1 photons[1] = 1 bs2 = pcvl.BasicState(photons) circuit = pcvl.Circuit(m) // pcvl.components.Unitary(u1) + tvds = {} result = [] + refbsd = None + ref = "-" for backend_name in backends: + # get_logger().warn(backend_name) if backend_name == "SLAP" and n > 15: print("\t-- ", end='', flush=True) continue @@ -44,28 +61,33 @@ def bench(m, n, backends): continue start = time.time() backend = pcvl.backends.BackendFactory.get_backend(backend_name) - if backend_name == "SLOS_V2_PS" or backend_name == "SLOS_V3_PS": - backend.set_post_select(ps) - pass - else: - backend.set_mask(mask_str, n) - pass + set_mask(backend, mask_str, m, n) backend.set_circuit(circuit) backend.set_input_state(bs) - bsd = backend.all_prob() + bsd = do_compute(backend, method) end = time.time() backend.set_input_state(bs2) - bsd = backend.all_prob() - # bsd = backend.prob_distribution() + bsd = do_compute(backend, method) end2 = time.time() + + # if backend_name == "SLOS_V2" or backend_name == "SLOS_V2_PS": + # print(bsd) + # if refbsd: + # tvds[backend_name] = tvd_dist(bsd, refbsd) + # pass + # else: + # refbsd = bsd + # ref = backend_name print(f"\t{end-start:.4f} {end2-end:.4f}", end='', flush=True) result.append(end - start) + for k, v in tvds.items(): + print(f"\n{k} TVD against {ref}: {v}", end='') return result -def bench_mem(m, n, backends): +def bench_mem(m, n, backends, mask_str, method): u1 = pcvl.Matrix.random_unitary(m) photons = [ 0 ] * m photons[0] = n @@ -78,14 +100,15 @@ def bench_mem(m, n, backends): gc.collect() mem_1 = psutil.Process().memory_info().rss backend = pcvl.backends.BackendFactory.get_backend(backend_name) + set_mask(backend, mask_str, m, n) backend.set_circuit(circuit) backend.set_input_state(bs) - bsd = backend.prob_distribution() + bsd = do_compute(backend, method) mem_2 = psutil.Process().memory_info().rss backend = None bsd = None gc.collect() - print(f"{backend_name}\t{mem_1}\t{mem_2}\t{human_readable_size(mem_2-mem_1)}") + print(f"{backend_name:12}\t{human_readable_size(mem_2-mem_1)}") if __name__ == "__main__": import argparse @@ -103,22 +126,31 @@ def bench_mem(m, n, backends): parser.add_argument('--backend', '-b', type=str, action='store', default='SLOS', help='backend used') + parser.add_argument('--mask', '-k', + type=str, action='store', default='', + help='mask') + parser.add_argument('--compute', '-c', + type=int, action='store', default='3', + help='Computations: 0 -> all_prob_ampli / 1 -> all_prob / 2 -> prob_distribution') args = parser.parse_args() backends = [ "SLOS", "SLAP", "SLOS_CPP", "SLOS_V2", - "SLOS_V2_PS", + # "SLOS_V2_PS", "SLOS_V3", - "SLOS_V3_PS", + # "SLOS_V3_PS", ] if args.memusage: - bench_mem(args.modes, args.nphotons, [args.backend]) + bench_mem(args.modes, args.nphotons, [args.backend], args.mask, args.compute) else: - print('\t', "\t".join(backends)) + print('\t', end = '', flush=False) + for b in backends: + print(f"{b:<16}", end = '', flush=False) + print('', flush=True) for n in range(args.minphotons, args.nphotons + 1): print(f"{n}", end='') - bench(args.modes, n, backends) + bench(args.modes, n, backends, args.mask, args.compute) print("") diff --git a/benchmarks-memory.sh b/benchmarks-memory.sh new file mode 100755 index 00000000..e249de53 --- /dev/null +++ b/benchmarks-memory.sh @@ -0,0 +1,9 @@ +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +echo $SCRIPT_DIR +python ${SCRIPT_DIR}/Bench_backends.py -m 20 -n 10 -u -b SLOS +python ${SCRIPT_DIR}/Bench_backends.py -m 20 -n 10 -u -b SLAP +python ${SCRIPT_DIR}/Bench_backends.py -m 20 -n 10 -u -b SLOS_CPP +python ${SCRIPT_DIR}/Bench_backends.py -m 20 -n 10 -u -b SLOS_V2 +# python ${SCRIPT_DIR}/Bench_backends.py -m 20 -n 10 -u -b SLOS_V2_PS +python ${SCRIPT_DIR}/Bench_backends.py -m 20 -n 10 -u -b SLOS_V3 +# python ${SCRIPT_DIR}/Bench_backends.py -m 20 -n 10 -u -b SLOS_V3_PS From 80cf638dc73cb33209b108881358d7ef3aa49698 Mon Sep 17 00:00:00 2001 From: Benoit Fanchon Date: Wed, 11 Feb 2026 16:41:03 +0100 Subject: [PATCH 07/11] fix --- Bench_backends.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Bench_backends.py b/Bench_backends.py index 3b2a12db..3987c541 100644 --- a/Bench_backends.py +++ b/Bench_backends.py @@ -33,7 +33,7 @@ def do_compute(backend, method): return backend.prob_distribution() def set_mask(backend, mask_str, m, n): - backend.set_mask((mask_str + "*" * (m-4))[:m], n) + backend.set_mask((mask_str + "*" * m)[:m], n) # if backend.name == "SLOS_V2_PS" or backend.name == "SLOS_V3_PS": # backend.set_post_select(ps) From bd46b9902bb4d2f0394d5b3193a3117b2430df4c Mon Sep 17 00:00:00 2001 From: Benoit Fanchon Date: Wed, 11 Feb 2026 16:58:19 +0100 Subject: [PATCH 08/11] fix --- perceval/backends/_slos.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/perceval/backends/_slos.py b/perceval/backends/_slos.py index 33a22765..8df63a54 100644 --- a/perceval/backends/_slos.py +++ b/perceval/backends/_slos.py @@ -192,6 +192,9 @@ def prob_amplitude(self, output_state: FockState) -> complex: result = self._state_mapping[self._input_state].coefs[output_idx, 0] * math.sqrt(output_state.prodnfact() / self._input_state.prodnfact()) return result if self._symb else complex(result) + def all_prob_ampli(self): + return self._state_mapping[self._input_state].coefs.reshape(self._fsas[self._input_state.n].count()) + def prob_distribution(self) -> BSDistribution: istate = self._input_state c = self._state_mapping[istate].coefs.reshape(self._fsas[istate.n].count()) From 090d490868c5be3b40883e4e19203d5bf9c5781c Mon Sep 17 00:00:00 2001 From: Benoit Fanchon Date: Wed, 11 Feb 2026 17:57:03 +0100 Subject: [PATCH 09/11] benchmarks tools --- Bench_backends.py | 51 ++++++++++++++-------------------- benchmarks-memory.sh | 65 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 77 insertions(+), 39 deletions(-) diff --git a/Bench_backends.py b/Bench_backends.py index 3987c541..bfc7e166 100644 --- a/Bench_backends.py +++ b/Bench_backends.py @@ -87,28 +87,27 @@ def bench(m, n, backends, mask_str, method): return result -def bench_mem(m, n, backends, mask_str, method): +def bench_mem(m, n, backend_name, mask_str, method): u1 = pcvl.Matrix.random_unitary(m) photons = [ 0 ] * m photons[0] = n bs = pcvl.BasicState(photons) circuit = pcvl.Circuit(m) // pcvl.components.Unitary(u1) - for backend_name in backends: - # if backend_name == "SLOS" and n > 12: - # break - gc.collect() - mem_1 = psutil.Process().memory_info().rss - backend = pcvl.backends.BackendFactory.get_backend(backend_name) - set_mask(backend, mask_str, m, n) - backend.set_circuit(circuit) - backend.set_input_state(bs) - bsd = do_compute(backend, method) - mem_2 = psutil.Process().memory_info().rss - backend = None - bsd = None - gc.collect() - print(f"{backend_name:12}\t{human_readable_size(mem_2-mem_1)}") + # if backend_name == "SLOS" and n > 12: + # break + gc.collect() + mem_1 = psutil.Process().memory_info().rss + backend = pcvl.backends.BackendFactory.get_backend(backend_name) + set_mask(backend, mask_str, m, n) + backend.set_circuit(circuit) + backend.set_input_state(bs) + bsd = do_compute(backend, method) + mem_2 = psutil.Process().memory_info().rss + backend = None + bsd = None + gc.collect() + print(f"{backend_name:12}\t{human_readable_size(mem_2-mem_1)}") if __name__ == "__main__": import argparse @@ -123,8 +122,8 @@ def bench_mem(m, n, backends, mask_str, method): type=int, action='store', default=4, help='number of modes') parser.add_argument('--memusage', '-u', action='store_true', default=False) - parser.add_argument('--backend', '-b', - type=str, action='store', default='SLOS', + parser.add_argument('--backends', '-b', + type=str, action='store', default='SLOS SLAP SLOS_CPP SLOS_V2 SLOS_V3', help='backend used') parser.add_argument('--mask', '-k', type=str, action='store', default='', @@ -133,24 +132,16 @@ def bench_mem(m, n, backends, mask_str, method): type=int, action='store', default='3', help='Computations: 0 -> all_prob_ampli / 1 -> all_prob / 2 -> prob_distribution') args = parser.parse_args() - backends = [ - "SLOS", - "SLAP", - "SLOS_CPP", - "SLOS_V2", - # "SLOS_V2_PS", - "SLOS_V3", - # "SLOS_V3_PS", - ] + backend_list = args.backends.split(" ") if args.memusage: - bench_mem(args.modes, args.nphotons, [args.backend], args.mask, args.compute) + bench_mem(args.modes, args.nphotons, backend_list[0], args.mask, args.compute) else: print('\t', end = '', flush=False) - for b in backends: + for b in backend_list: print(f"{b:<16}", end = '', flush=False) print('', flush=True) for n in range(args.minphotons, args.nphotons + 1): print(f"{n}", end='') - bench(args.modes, n, backends, args.mask, args.compute) + bench(args.modes, n, backend_list, args.mask, args.compute) print("") diff --git a/benchmarks-memory.sh b/benchmarks-memory.sh index e249de53..f57bbca7 100755 --- a/benchmarks-memory.sh +++ b/benchmarks-memory.sh @@ -1,9 +1,56 @@ -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -echo $SCRIPT_DIR -python ${SCRIPT_DIR}/Bench_backends.py -m 20 -n 10 -u -b SLOS -python ${SCRIPT_DIR}/Bench_backends.py -m 20 -n 10 -u -b SLAP -python ${SCRIPT_DIR}/Bench_backends.py -m 20 -n 10 -u -b SLOS_CPP -python ${SCRIPT_DIR}/Bench_backends.py -m 20 -n 10 -u -b SLOS_V2 -# python ${SCRIPT_DIR}/Bench_backends.py -m 20 -n 10 -u -b SLOS_V2_PS -python ${SCRIPT_DIR}/Bench_backends.py -m 20 -n 10 -u -b SLOS_V3 -# python ${SCRIPT_DIR}/Bench_backends.py -m 20 -n 10 -u -b SLOS_V3_PS + +#header#{{{# +scriptPath="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +advancedPath="${scriptPath}/.." +#}}}# + +## ## +## arguments ## +## ## + +modes="" +min_photons="4" +max_photons="" +# memusage=false +backends="SLOS SLAP SLOS_CPP SLOS_V2 SLOS_V3" +mask="" +compute_method=1 + +# message strings +USAGESTR="Usage: $0 [--modes/-m] 20 [--nphotons/-n] 10 [--minphotons/-i] 4 [--backends/-b] \"SLOS_V2 SLOS_V3\" [--mask/-k] \"1010**\" [--compute/-c] 2" +ERRORSTR="Failed to parse options... exiting." + +# option strings +SHORTOPTIONS="m:n:i:b:k:c:" +LONGOPTIONS="modes:,nphotons:,minphotons:,backends:,maks:,compute:" + +# read the options +ARGS=$(getopt --options $SHORTOPTIONS --longoptions $LONGOPTIONS --name "$0" -- "$@") || exit 1 +eval "set -- $ARGS" + +# extract options and their arguments into variables +while true ; do + case "$1" in + -h | --help ) echo "$USAGESTR" ; exit 0 ;; + -m | --modes ) modes="$2" ; shift 2 ;; + -n | --nphotons ) max_photons="$2" ; shift 2 ;; + -i | --minphoton ) min_photons="$2" ; shift 2 ;; + # -u | --memusage ) memusage=true ; shift ;; + -b | --backends ) backends="$2" ; shift 2 ;; + -k | --mask ) mask="$2" ; shift 2 ;; + -c | --compute ) compute="$2" ; shift 2 ;; + -- ) shift ; break ;; + *) echo "$ERRORSTR" >&2 ; exit 1 ;; + esac +done + +# echo ${backends} + +# check number of args left +# [[ $# -ne 1 ]] && echo "$ARGERRORSTR" >&2 && exit 1 + +# echo scriptPath $scriptPath +for backend in $backends +do + python ${scriptPath}/Bench_backends.py -u -m ${modes} -n ${max_photons} -i ${min_photons} -b "${backend}" -c ${compute} +done From b04cc1704e5d0464e1486dcf13d475924a8e9a5c Mon Sep 17 00:00:00 2001 From: Benoit Fanchon Date: Wed, 11 Feb 2026 18:26:31 +0100 Subject: [PATCH 10/11] fix --- benchmarks-memory.sh | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/benchmarks-memory.sh b/benchmarks-memory.sh index f57bbca7..2062a7db 100755 --- a/benchmarks-memory.sh +++ b/benchmarks-memory.sh @@ -9,20 +9,18 @@ advancedPath="${scriptPath}/.." ## ## modes="" -min_photons="4" -max_photons="" -# memusage=false +nphotons="4" backends="SLOS SLAP SLOS_CPP SLOS_V2 SLOS_V3" mask="" compute_method=1 # message strings -USAGESTR="Usage: $0 [--modes/-m] 20 [--nphotons/-n] 10 [--minphotons/-i] 4 [--backends/-b] \"SLOS_V2 SLOS_V3\" [--mask/-k] \"1010**\" [--compute/-c] 2" +USAGESTR="Usage: $0 [--modes/-m] 20 [--nphotons/-n] 10 [--backends/-b] \"SLOS_V2 SLOS_V3\" [--mask/-k] \"1010**\" [--compute/-c] 2" ERRORSTR="Failed to parse options... exiting." # option strings SHORTOPTIONS="m:n:i:b:k:c:" -LONGOPTIONS="modes:,nphotons:,minphotons:,backends:,maks:,compute:" +LONGOPTIONS="modes:,nphotons:,minphotons:,backends:,mask:,compute:" # read the options ARGS=$(getopt --options $SHORTOPTIONS --longoptions $LONGOPTIONS --name "$0" -- "$@") || exit 1 @@ -33,9 +31,7 @@ while true ; do case "$1" in -h | --help ) echo "$USAGESTR" ; exit 0 ;; -m | --modes ) modes="$2" ; shift 2 ;; - -n | --nphotons ) max_photons="$2" ; shift 2 ;; - -i | --minphoton ) min_photons="$2" ; shift 2 ;; - # -u | --memusage ) memusage=true ; shift ;; + -n | --nphotons ) nphotons="$2" ; shift 2 ;; -b | --backends ) backends="$2" ; shift 2 ;; -k | --mask ) mask="$2" ; shift 2 ;; -c | --compute ) compute="$2" ; shift 2 ;; @@ -44,13 +40,7 @@ while true ; do esac done -# echo ${backends} - -# check number of args left -# [[ $# -ne 1 ]] && echo "$ARGERRORSTR" >&2 && exit 1 - -# echo scriptPath $scriptPath for backend in $backends do - python ${scriptPath}/Bench_backends.py -u -m ${modes} -n ${max_photons} -i ${min_photons} -b "${backend}" -c ${compute} + python ${scriptPath}/Bench_backends.py -u -m ${modes} -n ${nphotons} -b "${backend}" -c ${compute} -k "$mask" done From 8778e90557e81efea321d2f5aa7674059dd988ad Mon Sep 17 00:00:00 2001 From: Benoit Fanchon Date: Wed, 11 Feb 2026 18:46:12 +0100 Subject: [PATCH 11/11] again --- Bench_backends.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Bench_backends.py b/Bench_backends.py index bfc7e166..0a1a70ee 100644 --- a/Bench_backends.py +++ b/Bench_backends.py @@ -33,7 +33,8 @@ def do_compute(backend, method): return backend.prob_distribution() def set_mask(backend, mask_str, m, n): - backend.set_mask((mask_str + "*" * m)[:m], n) + if mask_str: + backend.set_mask((mask_str + "*" * m)[:m], n) # if backend.name == "SLOS_V2_PS" or backend.name == "SLOS_V3_PS": # backend.set_post_select(ps)