From a62d55778ea20445b460f01ff6b94a473e6e9f21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lienBaert?= Date: Mon, 29 Jun 2026 16:36:59 +0200 Subject: [PATCH 1/2] Use SimulatedComputer inside Processor + Removed Processor where it created import loops --- perceval/components/__init__.py | 3 +- perceval/components/experiment.py | 29 +--- perceval/runtime/__init__.py | 18 +- perceval/runtime/abstract_processor.py | 13 +- perceval/runtime/processor.py | 159 ++++-------------- perceval/runtime/remote_computer.py | 11 ++ perceval/runtime/remote_processor.py | 13 -- perceval/simulators/feed_forward_simulator.py | 44 ++--- perceval/simulators/simulator_factory.py | 4 +- tests/utils/test_log.py | 2 +- 10 files changed, 80 insertions(+), 216 deletions(-) diff --git a/perceval/components/__init__.py b/perceval/components/__init__.py index e9adefe2a..c11b97144 100644 --- a/perceval/components/__init__.py +++ b/perceval/components/__init__.py @@ -48,7 +48,8 @@ catalog = Catalog('perceval.components.core_catalog') # TODO: remove in perceval 1.4 -from perceval.runtime import AProcessor, Processor as Proc +from perceval.runtime.abstract_processor import AProcessor +from perceval.runtime.processor import Processor as Proc from perceval.utils import deprecated, ProcessorType class Processor(Proc): diff --git a/perceval/components/experiment.py b/perceval/components/experiment.py index f99ce70bf..0a43bebc1 100644 --- a/perceval/components/experiment.py +++ b/perceval/components/experiment.py @@ -75,7 +75,7 @@ class Experiment: :param name: The experiment name """ - _no_copiable_attributes = { '_circuit_changed_observers', '_noise_changed_observers', '_input_changed_observers' } + _no_copiable_attributes = { '_noise_changed_observers' } def __init__(self, m_circuit: int | ACircuit = None, noise: NoiseModel = None, name: str = "Experiment"): self._input_state = None @@ -84,9 +84,7 @@ def __init__(self, m_circuit: int | ACircuit = None, noise: NoiseModel = None, n self._min_detected_photons_filter: int | None = None # TODO: Legacy - Remove when removing Processors - self._circuit_changed_observers: list[Callable[[Experiment | AComponent | None], None]] = [] self._noise_changed_observers: list[Callable[[], None]] = [] - self._input_changed_observers: list[Callable[[], None]] = [] self.noise: NoiseModel | None = noise @@ -143,21 +141,11 @@ def clear_input_and_circuit(self, new_m=None): get_logger().debug(f"Clear input and circuit in experiment {self.name}", channel.general) self._reset_circuit() self._input_state = None - self._input_changed() - self._circuit_changed() if new_m is not None: self.m = new_m - def _circuit_changed(self, component=None): - for observer in self._circuit_changed_observers: - observer_fn = observer() - if observer_fn is not None: - observer_fn(component) # Used to notify the Processors containing this experiment of a new component - - def add_observers(self, circuit_observer: callable, noise_observer: callable, input_observer: callable): - self._circuit_changed_observers.append(weakref.WeakMethod(circuit_observer)) + def add_observers(self, noise_observer: callable): self._noise_changed_observers.append(weakref.WeakMethod(noise_observer)) - self._input_changed_observers.append(weakref.WeakMethod(input_observer)) def min_detected_photons_filter(self, n: int): r""" @@ -214,12 +202,10 @@ def set_postselection(self, postselect: PostSelect | str): if not isinstance(postselect, PostSelect): raise TypeError("Parameter must be a PostSelect object") - self._circuit_changed() self._postselect = postselect def clear_postselection(self): if self._postselect != PostSelect(): - self._circuit_changed() self._postselect = PostSelect() def __deepcopy__(self, memo): @@ -317,7 +303,6 @@ def add(self, mode_mapping, component, keep_port: bool = True): else: raise RuntimeError(f"Cannot add {type(component)} object to an Experiment") - self._circuit_changed(component) return self def _add_ffconfig(self, modes, component: AFFConfigurator): @@ -568,7 +553,6 @@ def _add_herald(self, mode, herald: Herald, location: PortLocation = PortLocatio if location == PortLocation.OUTPUT or location == PortLocation.IN_OUT: self._out_ports[herald] = [mode] self._out_mode_type[mode] = ModeType.HERALD - self._circuit_changed() def add_herald(self, mode: int, expected: int, name: str = None, location: PortLocation = PortLocation.IN_OUT): r""" @@ -838,12 +822,6 @@ def check_input(self, input_state: FockState): assert len(input_state) == expected_input_length, \ f"Input length not compatible with circuit (expects {expected_input_length}, got {len(input_state)})" - def _input_changed(self): - for observer in self._input_changed_observers: - observer_fn = observer() - if observer_fn is not None: - observer_fn() - @dispatch(LogicalState) def with_input(self, input_state: LogicalState): input_state = get_basic_state_from_ports(list(self._in_ports.keys()), input_state) @@ -865,13 +843,11 @@ def with_input(self, input_state: FockState) -> None: input_idx += 1 self._input_state = FockState(input_list) - self._input_changed() @dispatch(AnnotatedFockState) def with_input(self, input_state: AnnotatedFockState) -> None: if input_state.has_polarization: self._input_state = input_state - self._input_changed() else: raise TypeError("Local simulations only support AnnotatedFockState in case of a polarized input state") @@ -895,7 +871,6 @@ def with_input(self, svd: SVDistribution): assert self.m is not None, "A circuit has to be set before the input distribution" assert svd.m == self.circuit_size, f'Input distribution contains states with a bad size ({svd.m}), expected {self.circuit_size}' self._input_state = svd - self._input_changed() def flatten(self, max_depth=None) -> list[tuple]: """ diff --git a/perceval/runtime/__init__.py b/perceval/runtime/__init__.py index e0b8ab916..7e29b1c3c 100644 --- a/perceval/runtime/__init__.py +++ b/perceval/runtime/__init__.py @@ -28,15 +28,7 @@ # SOFTWARE. from .job_status import JobStatus, RunningStatus -from .job import Job -from .local_job import LocalJob -from .remote_job import RemoteJob -from .abstract_processor import AProcessor -from .processor import Processor -from .remote_processor import RemoteProcessor, perf_dict_to_noise -from .session import ISession from .remote_config import RemoteConfig -from .job_group import JobGroup from .check_cancel import cancel_requested from .payload_generator import PayloadGenerator from .payload_updater import PayloadUpdater @@ -49,3 +41,13 @@ from .simulated_computer import SimulatedComputer from .remote_computer import RemoteComputer, CommunicationLayer from .quandela_computer import QuandelaComputer, QuandelaCommunicationLayer + +# Legacy +from .session import ISession +from .job import Job +from .local_job import LocalJob +from .remote_job import RemoteJob +from .job_group import JobGroup +from .abstract_processor import AProcessor +from .processor import Processor +from .remote_processor import RemoteProcessor, perf_dict_to_noise diff --git a/perceval/runtime/abstract_processor.py b/perceval/runtime/abstract_processor.py index 108fe4835..58e5df352 100644 --- a/perceval/runtime/abstract_processor.py +++ b/perceval/runtime/abstract_processor.py @@ -50,24 +50,13 @@ def experiment(self) -> Experiment: @experiment.setter def experiment(self, experiment: Experiment): self._experiment = experiment - experiment.add_observers(self._circuit_change_observer, - self._noise_changed_observer, - self._input_changed_observer) + experiment.add_observers(self._noise_changed_observer) self._noise_changed_observer() - self._circuit_change_observer() - self._input_changed_observer() - - @abstractmethod - def _circuit_change_observer(self, new_component = None): - pass @abstractmethod def _noise_changed_observer(self): pass - def _input_changed_observer(self): - pass - @property def name(self) -> str: return self.experiment.name diff --git a/perceval/runtime/processor.py b/perceval/runtime/processor.py index 12bf9078e..7dc20b6de 100644 --- a/perceval/runtime/processor.py +++ b/perceval/runtime/processor.py @@ -28,15 +28,13 @@ # SOFTWARE. import sys -from perceval.utils import SVDistribution, BasicState, FockState, AnnotatedFockState, StateVector, NoiseModel, \ - ProcessorType, ProgressCallback -from perceval.utils.logging import get_logger, channel - -from perceval.runtime.abstract_processor import AProcessor +from perceval.utils import SVDistribution, FockState, NoiseModel, ProcessorType, ProgressCallback from perceval.components.experiment import Experiment from perceval.components.linear_circuit import ACircuit, Circuit from perceval.components.source import Source -from perceval.backends import ExqaliburBackendWrapper, ASamplingBackend + +from .abstract_processor import AProcessor +from .simulated_computer import SimulatedComputer class Processor(AProcessor): @@ -64,51 +62,46 @@ def __init__(self, backend, m_circuit: int | ACircuit | Experiment = None, m_circuit = Experiment(m_circuit, noise=noise, name=name) elif noise: m_circuit = m_circuit.copy() # Create a copy so that we don't change the input experiment - m_circuit.noise = noise super().__init__(m_circuit) - self._init_backend(backend) - self._inputs_map = None - self._noise_changed_observer() - self._input_changed_observer() - self._simulator = None - self._compute_physical_logical_perf = False + self._init_backend(backend) # the only reason to keep this is that backend is public + self._computer = SimulatedComputer(self.backend) - @property - def _has_custom_input(self): - return (isinstance(self.input_state, SVDistribution) - or (isinstance(self.input_state, AnnotatedFockState) and self.input_state.has_polarization)) + if noise is not None: + self._computer.noise = noise + self._noise_changed_observer() def _noise_changed_observer(self): - self._source = Source.from_noise_model(self.noise) - if not self._has_custom_input: - self._inputs_map = None + self._source = None @AProcessor.noise.getter def noise(self): noise = super(Processor, type(self)).noise.fget(self) if noise is None: - return NoiseModel() + return self._computer.noise return noise + @AProcessor.noise.setter + def noise(self, noise: NoiseModel): + self._computer.noise = noise + @property def source_distribution(self) -> SVDistribution | None: r""" Retrieve the computed input distribution. Compute it if it is not cached and an input state has been provided. :return: the input SVDistribution if `with_input` was called previously, otherwise None. """ - if self._inputs_map is None and self.input_state is not None: - self._generate_noisy_input() - return self._inputs_map - - def _circuit_change_observer(self, new_component = None): - self._simulator = None + if isinstance(self.input_state, FockState): + return self.source.generate_distribution(self.input_state) + return self._experiment.input_state @property def source(self): """ :return: The photonic source """ + if self._source is None: + self._source = Source.from_noise_model(self.noise) return self._source def _init_backend(self, backend): @@ -128,28 +121,12 @@ def type(self) -> ProcessorType: def is_remote(self) -> bool: return False - def _generate_noisy_input(self): - self._inputs_map = self._source.generate_distribution(self.input_state) - def generate_noisy_heralds(self) -> SVDistribution: - if self.heralds: + if self.in_heralds: heralds_perfect_state = FockState([v for k, v in sorted(self.experiment.in_heralds.items())]) - return self._source.generate_distribution(heralds_perfect_state) + return self.source.generate_distribution(heralds_perfect_state) return SVDistribution() - def _input_changed_observer(self): - if isinstance(self.input_state, BasicState): - if isinstance(self.input_state, AnnotatedFockState): - self._inputs_map = SVDistribution(StateVector(self.input_state)) - else: - self._inputs_map = None - elif isinstance(self.input_state, SVDistribution): - self._inputs_map = self.input_state - - def clear_input_and_circuit(self, new_m=None): - super().clear_input_and_circuit(new_m) - self._inputs_map = None - def linear_circuit(self, flatten: bool = False) -> Circuit: """ Creates a linear circuit from internal components, if all internal components are unitary. Takes phase @@ -163,102 +140,24 @@ def linear_circuit(self, flatten: bool = False) -> Circuit: return experiment.unitary_circuit(flatten=flatten) def samples(self, max_samples: int, max_shots: int = None, progress_callback=None) -> dict: - self.check_min_detected_photons_filter() - - # Avoids circular import - from perceval.simulators import ExqaliburNoisySamplingSimulator, NoisySamplingSimulator - - assert isinstance(self.backend, ASamplingBackend), "A sampling backend is required to call samples method" - 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( - min_detected_photons_filter=self._min_detected_photons_filter, - postselect=self.post_select_fn, - heralds=self.heralds) - sampling_simulator.keep_heralds(False) - sampling_simulator.compute_physical_logical_perf(self._compute_physical_logical_perf) - sampling_simulator.set_detectors(self.detectors) - self.log_resources(sys._getframe().f_code.co_name, {'max_samples': max_samples, 'max_shots': max_shots}) - get_logger().info( - f"Start a local {'perfect' if self._source.is_perfect() else 'noisy'} sampling", channel.general) - sample_provider = self.source_distribution if self._has_custom_input else (self._source, self.input_state) - res = sampling_simulator.samples(sample_provider, max_samples, max_shots, progress_callback) - get_logger().info("Local sampling complete!", channel.general) - return res + # Experiment's noise takes precedence + with self._computer.apply_configuration(noise = self.experiment.noise): + return self._computer.samples(self.experiment, max_samples=max_samples, max_shots=max_shots, progress_callback=progress_callback) def probs(self, precision: float = None, progress_callback: ProgressCallback = None) -> dict: - self.check_min_detected_photons_filter() - - experiment = self.experiment.use_phase_noise(self.noise) - - # assert self._inputs_map is not None, "Input is missing, please call with_inputs()" - if self._simulator is None: - from perceval.simulators import SimulatorFactory # Avoids a circular import - self._simulator = SimulatorFactory.build(experiment, self.backend) - else: - self._simulator.set_circuit(experiment.unitary_circuit() if experiment.is_unitary else experiment.components, - experiment.circuit_size) - self._simulator.set_min_detected_photons_filter(self._min_detected_photons_filter) - - if precision is not None: - self._simulator.set_precision(precision) - get_logger().info(f"Start a local {'perfect' if self._source.is_perfect() else 'noisy'} strong simulation", - channel.general) - self._simulator.keep_heralds(False) - self._simulator.compute_physical_logical_perf(self._compute_physical_logical_perf) - svd = self.source_distribution if self._has_custom_input else (self._source, self.input_state) - res = self._simulator.probs_svd(svd, self.detectors, progress_callback) - get_logger().info("Local strong simulation complete!", channel.general) - - self.log_resources(sys._getframe().f_code.co_name, {'precision': precision}) - return res + # Experiment's noise takes precedence + with self._computer.apply_configuration(noise=self.experiment.noise): + return self._computer.probs(self.experiment, precision=precision, progress_callback=progress_callback) @property def available_commands(self) -> list[str]: from perceval.backends import ASamplingBackend return ["samples" if isinstance(self.backend, ASamplingBackend) else "probs"] - def log_resources(self, method: str, extra_parameters: dict): - """Log resources of the processor - - :param method: name of the method used - :param extra_parameters: extra parameters to log. - - Extra parameter can be: - - - max_samples - - max_shots - - precision - """ - extra_parameters = {key: value for key, value in extra_parameters.items() if value is not None} - my_dict = { - 'layer': 'Processor', - 'backend': self.backend.name, - 'm': self.circuit_size, - 'method': method - } - if isinstance(self.input_state, BasicState): - my_dict['n'] = self.input_state.n - elif isinstance(self.input_state, StateVector): - my_dict['n'] = max(self.input_state.n) - elif isinstance(self.input_state, SVDistribution): - my_dict['n'] = self.input_state.n_max - else: - get_logger().error(f"Cannot get n for type {type(self.input_state).__name__}", channel.general) - if extra_parameters: - my_dict.update(extra_parameters) - if self.noise != NoiseModel(): - my_dict['noise'] = self.noise.__dict__() - get_logger().log_resources(my_dict) - 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 + self._computer.compute_physical_logical_perf(value) diff --git a/perceval/runtime/remote_computer.py b/perceval/runtime/remote_computer.py index bcce607c6..4c93df968 100644 --- a/perceval/runtime/remote_computer.py +++ b/perceval/runtime/remote_computer.py @@ -227,6 +227,17 @@ def check_experiment(self, experiment: Experiment) -> None: if 'min_mode_count' in constraints and m < constraints['min_mode_count']: raise RuntimeError(f"Circuit too small ({m} < {constraints['min_mode_count']})") + # TODO: Check that the component matches what the platform can do + # if new_component is not None: + # if isinstance(new_component, Experiment): + # if not new_component.is_unitary: + # raise RuntimeError('Cannot compose a RemoteProcessor with a processor containing non linear components') + # if new_component.has_feedforward: + # raise RuntimeError('Cannot compose a RemoteProcessor with a processor containing feed-forward') + # + # elif not isinstance(new_component, IDetector) and not isinstance(new_component, ACircuit): + # raise NotImplementedError("Non linear components not implemented for RemoteProcessors") + def _handle_iterator(self, comp: Computation | ComputationIterator, out: dict | None)\ -> tuple[dict, Callable[[dict], None]]: if out is None: diff --git a/perceval/runtime/remote_processor.py b/perceval/runtime/remote_processor.py index 9b7991f3c..68899022b 100644 --- a/perceval/runtime/remote_processor.py +++ b/perceval/runtime/remote_processor.py @@ -131,19 +131,6 @@ def name(self, name: str): self._name = name self._experiment.name = name - def _circuit_change_observer(self, new_component: Experiment | AComponent = None): - pass - # TODO: Check that the component matches what the platform can do - # if new_component is not None: - # if isinstance(new_component, Experiment): - # if not new_component.is_unitary: - # raise RuntimeError('Cannot compose a RemoteProcessor with a processor containing non linear components') - # if new_component.has_feedforward: - # raise RuntimeError('Cannot compose a RemoteProcessor with a processor containing feed-forward') - # - # elif not isinstance(new_component, IDetector) and not isinstance(new_component, ACircuit): - # raise NotImplementedError("Non linear components not implemented for RemoteProcessors") - def _noise_changed_observer(self): if self.noise and self.type == ProcessorType.PHYSICAL: # Injecting a noise model to an actual QPU makes no sense get_logger().warn( diff --git a/perceval/simulators/feed_forward_simulator.py b/perceval/simulators/feed_forward_simulator.py index aba44bec5..14d72230e 100644 --- a/perceval/simulators/feed_forward_simulator.py +++ b/perceval/simulators/feed_forward_simulator.py @@ -35,7 +35,6 @@ from perceval.utils.logging import channel from perceval.components.feed_forward_configurator import AFFConfigurator from perceval.backends import AStrongSimulationBackend, IFFBackend -from perceval.runtime import Processor from .simulator_interface import ISimulator @@ -56,7 +55,8 @@ def compute_physical_logical_perf(self, value: bool): if value: get_logger().warn("Only the global performance can be computed for a feed-forward simulator.") - def set_circuit(self, circuit: Processor | Experiment | list[tuple[tuple, AComponent]], m=None): + def set_circuit(self, circuit: Experiment | list[tuple[tuple, AComponent]], m=None): + from perceval.runtime import Processor if isinstance(circuit, (Processor, Experiment)): self._components = circuit.components min_detected_photons = circuit._min_detected_photons_filter @@ -214,7 +214,7 @@ def _find_next_simulation_layer(self) -> tuple[list[tuple[int, AFFConfigurator]] def _get_sim_params(self, input_state: SVDistribution | tuple[Source, BasicState], - components: list[tuple[tuple, AComponent | Processor | Experiment]], + components: list[tuple[tuple, AComponent | Experiment]], m: int, detectors: list[IDetector] = None, filter_states: bool = False, @@ -223,7 +223,7 @@ def _get_sim_params(self, Heralds that are already in this simulator are still considered. :param input_state: The input state used for the simulation - :param components: A list of components that will be added in the simulation. Can themselves be processors. + :param components: A list of components that will be added in the simulation. Can themselves be experiments. :param filter_states: Whether the states should be filtered in the sub-simulation. :param new_heralds: The list of heralds that should be added, containing the position and the value @@ -232,57 +232,57 @@ def _get_sim_params(self, if detectors is None: detectors = m * [None] - proc = Processor(self._backend, m) - if self._noise_model is not None: - proc.noise = self._noise_model + exp = Experiment(m) for r, c in components: - proc.add(r, c) + exp.add(r, c) - # Now the Processor has only the heralds that were possibly added by adding Processors as input, all at the end + # Now the Experiment has only the heralds that were possibly added by adding Experiments as input, all at the end if isinstance(input_state, SVDistribution): - heralded_dist = proc.generate_noisy_heralds() - if len(heralded_dist): + if len(exp.in_heralds) > 0: + source = Source.from_noise_model(self._noise_model) + heralds_perfect_state = FockState([v for k, v in sorted(exp.in_heralds.items())]) + heralded_dist = source.generate_distribution(heralds_perfect_state) input_state = input_state * heralded_dist # Must not change the original object else: - proc.with_input(input_state[1]) - input_state = (input_state[0], proc.input_state) + exp.with_input(input_state[1]) + input_state = (input_state[0], exp.input_state) sum_new_heralds = 0 if new_heralds is not None: for r, v in new_heralds.items(): - proc.add_port(r, Herald(v), PortLocation.OUTPUT) + exp.add_port(r, Herald(v), PortLocation.OUTPUT) sum_new_heralds += v if filter_states: if self._heralds is not None: for r, v in self._heralds.items(): - proc.add_port(r, Herald(v), PortLocation.OUTPUT) + exp.add_port(r, Herald(v), PortLocation.OUTPUT) if self._postselect.has_condition: postselect = copy.copy(self._postselect) - postselect.merge(proc.post_select_fn) - proc.set_postselection(postselect) + postselect.merge(exp.post_select_fn) + exp.set_postselection(postselect) # We need to retrieve the new heralds as they are actually counting user photons - proc.min_detected_photons_filter(self._min_detected_photons_filter - sum_new_heralds) + exp.min_detected_photons_filter(self._min_detected_photons_filter - sum_new_heralds) else: # In that case, the new heralds are user-defined heralds that we can safely simulate. # We can't filter more due to potential losses that would depend on the FF parts of the circuit - proc.min_detected_photons_filter(0) + exp.min_detected_photons_filter(0) from .simulator_factory import SimulatorFactory # Avoids a circular import - sim = SimulatorFactory.build(proc) + sim = SimulatorFactory.build(exp, self._backend) if self._precision is not None: sim.set_precision(self._precision) sim.set_silent(True) - return sim, input_state, detectors + proc.detectors[m:] + return sim, input_state, detectors + exp.detectors[m:] def _simulate(self, input_state: SVDistribution | tuple[Source, BasicState], - components: list[tuple[tuple, AComponent | Processor]], + components: list[tuple[tuple, AComponent | Experiment]], m: int, detectors: list[IDetector], prog_cb: ProgressCallback = None, diff --git a/perceval/simulators/simulator_factory.py b/perceval/simulators/simulator_factory.py index a29060e8e..4f1413765 100644 --- a/perceval/simulators/simulator_factory.py +++ b/perceval/simulators/simulator_factory.py @@ -37,7 +37,6 @@ from ._simulator_utils import _unitary_components_to_circuit from perceval.components import ACircuit, TD, LC, Experiment, AFFConfigurator from perceval.backends import ABackend, SLOSExqaliburBackend, BACKEND_LIST, ExqaliburBackendWrapper -from perceval.runtime import Processor class SimulatorFactory: @@ -48,7 +47,7 @@ class SimulatorFactory: """ @staticmethod - def build(circuit: ACircuit | Processor | Experiment | list, + def build(circuit: ACircuit | Experiment | list, backend: ABackend | str = None, **kwargs) -> ISimulator: """ @@ -72,6 +71,7 @@ def build(circuit: ACircuit | Processor | Experiment | list, noise = None m = None + from perceval.runtime import Processor if isinstance(circuit, Processor): if backend is None: # If no backend was chosen, the backend type set in the Processor is used diff --git a/tests/utils/test_log.py b/tests/utils/test_log.py index 5b9b7e3df..87c89e82a 100644 --- a/tests/utils/test_log.py +++ b/tests/utils/test_log.py @@ -115,7 +115,7 @@ def test_log_resources(mock_info): my_dict = _get_last_dict_logged(mock_info.mock_calls[-1].args[0]) assert SOURCE not in my_dict assert my_dict[NOISE] == noise_model.__dict__() - assert my_dict[LAYER] == 'Processor' + assert my_dict[LAYER] == 'SimulatedComputer' assert my_dict[BACKEND] == 'SLOS' assert my_dict[N] == input_state.n assert my_dict[M] == circuit.m From fbb147be2c945b4cfaa8d11cc513697575e1efef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lienBaert?= Date: Fri, 3 Jul 2026 12:54:03 +0200 Subject: [PATCH 2/2] Changed noise behaviour for retrocompatibility --- perceval/runtime/processor.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/perceval/runtime/processor.py b/perceval/runtime/processor.py index 7dc20b6de..2d47918bc 100644 --- a/perceval/runtime/processor.py +++ b/perceval/runtime/processor.py @@ -74,15 +74,17 @@ def __init__(self, backend, m_circuit: int | ACircuit | Experiment = None, def _noise_changed_observer(self): self._source = None - @AProcessor.noise.getter + @property def noise(self): noise = super(Processor, type(self)).noise.fget(self) if noise is None: return self._computer.noise return noise - @AProcessor.noise.setter + @noise.setter def noise(self, noise: NoiseModel): + if self._experiment.noise is not None: + self._experiment.noise = noise self._computer.noise = noise @property