From 43a03514ae3fced6880f9182be718158e2c921c4 Mon Sep 17 00:00:00 2001 From: Charles MOUSSA Date: Tue, 12 Nov 2024 13:00:48 +0100 Subject: [PATCH 01/22] add try except when appending config noise --- qadence/backends/pyqtorch/backend.py | 58 ++++++++++++++++++++---- qadence/backends/pyqtorch/convert_ops.py | 7 ++- qadence/noise/protocols.py | 2 +- 3 files changed, 54 insertions(+), 13 deletions(-) diff --git a/qadence/backends/pyqtorch/backend.py b/qadence/backends/pyqtorch/backend.py index 31aeaa87..ecc5b911 100644 --- a/qadence/backends/pyqtorch/backend.py +++ b/qadence/backends/pyqtorch/backend.py @@ -28,6 +28,7 @@ flatten, invert_endianness, scale_primitive_blocks_only, + set_noise, transpile, ) from qadence.types import BackendName, Endianness, Engine @@ -38,6 +39,48 @@ logger = getLogger(__name__) +def converted_circuit_with_noise( + circuit: ConvertedCircuit, noise: NoiseHandler | None, config: Configuration +) -> ConvertedCircuit: + """For backend functions, get a ConvertedCircuit with noise. + + Note that if config.noise is not None, we try to append + + Args: + circuit (ConvertedCircuit): Original ConvertedCircuit. + noise (NoiseHandler | None): Noise to add. + + Returns: + ConvertedCircuit: A new ConvertedCircuit with noise. + """ + + if noise: + noise_combination = noise + if config.noise: + try: + noise_combination.append(config.noise) + except ValueError as e: + raise ValueError(f"Cannot append provided noise with the config noise. Error: {e}") + new_convcirc = ConvertedCircuit(circuit.native, circuit.abstract, circuit.original) + set_noise(new_convcirc.abstract, noise) + ops = convert_block( + new_convcirc.abstract.block, n_qubits=new_convcirc.abstract.n_qubits, config=config + ) + readout = None + if new_convcirc.native.readout_noise is None: + readout = convert_readout_noise(new_convcirc.abstract.n_qubits, noise) + native = pyq.QuantumCircuit( + new_convcirc.abstract.n_qubits, + ops, + readout, + ) + new_convcirc.native = native + + return new_convcirc + + return circuit + + @dataclass(frozen=True, eq=True) class Backend(BackendInterface): """PyQTorch backend.""" @@ -87,7 +130,8 @@ def observable(self, observable: AbstractBlock, n_qubits: int) -> ConvertedObser scale_primitive_blocks_only, ] block = transpile(*transpilations)(observable) # type: ignore[call-overload] - operations = convert_block(block, n_qubits, self.config) + # prevent setting noise on observable + operations = convert_block(block, n_qubits, self.config, False) native = pyq.Observable(operations=operations) return ConvertedObservable(native=native, abstract=block, original=observable) @@ -124,9 +168,7 @@ def _batched_expectation( noise: NoiseHandler | None = None, endianness: Endianness = Endianness.BIG, ) -> Tensor: - if noise and circuit.native.readout_noise is None: - readout = convert_readout_noise(circuit.abstract.n_qubits, noise) - circuit.native.readout_noise = readout + circuit = converted_circuit_with_noise(circuit, noise, self.config) state = self.run( circuit, param_values=param_values, @@ -164,9 +206,7 @@ def _looped_expectation( "Define your initial state with `batch_size=1`" ) - if noise and circuit.native.readout_noise is None: - readout = convert_readout_noise(circuit.abstract.n_qubits, noise) - circuit.native.readout_noise = readout + circuit = converted_circuit_with_noise(circuit, noise, self.config) list_expvals = [] observables = observable if isinstance(observable, list) else [observable] @@ -222,9 +262,7 @@ def sample( elif state is not None and pyqify_state: n_qubits = circuit.abstract.n_qubits state = pyqify(state, n_qubits) if pyqify_state else state - if noise and circuit.native.readout_noise is None: - readout = convert_readout_noise(circuit.abstract.n_qubits, noise) - circuit.native.readout_noise = readout + circuit = converted_circuit_with_noise(circuit, noise, self.config) samples: list[Counter] = circuit.native.sample( state=state, values=param_values, n_shots=n_shots ) diff --git a/qadence/backends/pyqtorch/convert_ops.py b/qadence/backends/pyqtorch/convert_ops.py index 362cb564..a77321c6 100644 --- a/qadence/backends/pyqtorch/convert_ops.py +++ b/qadence/backends/pyqtorch/convert_ops.py @@ -175,7 +175,10 @@ def fn(x: str | ConcretizedCallable, y: str | ConcretizedCallable) -> Callable: def convert_block( - block: AbstractBlock, n_qubits: int = None, config: Configuration = None + block: AbstractBlock, + n_qubits: int = None, + config: Configuration = None, + apply_noise: bool = True, ) -> Sequence[Module | Tensor | str | sympy.Expr]: if isinstance(block, (Tensor, str, sympy.Expr)): # case for hamevo generators if isinstance(block, Tensor): @@ -189,7 +192,7 @@ def convert_block( config = Configuration() noise: NoiseHandler | None = None - if hasattr(block, "noise") and block.noise: + if apply_noise and hasattr(block, "noise") and block.noise: noise = convert_digital_noise(block.noise) if isinstance(block, ScaleBlock): diff --git a/qadence/noise/protocols.py b/qadence/noise/protocols.py index 99fc1154..4a42bcff 100644 --- a/qadence/noise/protocols.py +++ b/qadence/noise/protocols.py @@ -155,7 +155,7 @@ def filter(self, protocol: NoiseEnum | str) -> NoiseHandler | None: list(compress(self.protocol, is_protocol)), list(compress(self.options, is_protocol)), ) - if len(is_protocol) > 0 + if len(is_protocol) > 0 and sum(is_protocol) > 0 else None ) From 6f4ef20a5292cd2fa39eeb55dfbc74546594ed73 Mon Sep 17 00:00:00 2001 From: Charles MOUSSA Date: Tue, 12 Nov 2024 13:25:13 +0100 Subject: [PATCH 02/22] set_noise in backend circuit method --- qadence/backends/pyqtorch/backend.py | 17 +++++++++++------ qadence/backends/pyqtorch/convert_ops.py | 16 ++++++++++++++-- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/qadence/backends/pyqtorch/backend.py b/qadence/backends/pyqtorch/backend.py index ecc5b911..4b453c40 100644 --- a/qadence/backends/pyqtorch/backend.py +++ b/qadence/backends/pyqtorch/backend.py @@ -44,7 +44,8 @@ def converted_circuit_with_noise( ) -> ConvertedCircuit: """For backend functions, get a ConvertedCircuit with noise. - Note that if config.noise is not None, we try to append + Note that if config.noise is not None, we try to append to noise and + this may raise an error. Args: circuit (ConvertedCircuit): Original ConvertedCircuit. @@ -62,13 +63,15 @@ def converted_circuit_with_noise( except ValueError as e: raise ValueError(f"Cannot append provided noise with the config noise. Error: {e}") new_convcirc = ConvertedCircuit(circuit.native, circuit.abstract, circuit.original) - set_noise(new_convcirc.abstract, noise) + set_noise(new_convcirc.abstract, noise_combination) + + # the config should not add the noise ops = convert_block( new_convcirc.abstract.block, n_qubits=new_convcirc.abstract.n_qubits, config=config ) - readout = None + readout = new_convcirc.native.readout_noise if new_convcirc.native.readout_noise is None: - readout = convert_readout_noise(new_convcirc.abstract.n_qubits, noise) + readout = convert_readout_noise(new_convcirc.abstract.n_qubits, noise_combination) native = pyq.QuantumCircuit( new_convcirc.abstract.n_qubits, ops, @@ -105,6 +108,8 @@ def circuit(self, circuit: QuantumCircuit) -> ConvertedCircuit: original_circ = circuit if len(passes) > 0: circuit = transpile(*passes)(circuit) + # setting noise on blocks + set_noise(circuit, self.config.noise) ops = convert_block(circuit.block, n_qubits=circuit.n_qubits, config=self.config) readout_noise = ( @@ -130,8 +135,8 @@ def observable(self, observable: AbstractBlock, n_qubits: int) -> ConvertedObser scale_primitive_blocks_only, ] block = transpile(*transpilations)(observable) # type: ignore[call-overload] - # prevent setting noise on observable - operations = convert_block(block, n_qubits, self.config, False) + # we do not set noise on the observable blocks + operations = convert_block(block, n_qubits, self.config) native = pyq.Observable(operations=operations) return ConvertedObservable(native=native, abstract=block, original=observable) diff --git a/qadence/backends/pyqtorch/convert_ops.py b/qadence/backends/pyqtorch/convert_ops.py index a77321c6..caffc1bb 100644 --- a/qadence/backends/pyqtorch/convert_ops.py +++ b/qadence/backends/pyqtorch/convert_ops.py @@ -178,8 +178,20 @@ def convert_block( block: AbstractBlock, n_qubits: int = None, config: Configuration = None, - apply_noise: bool = True, ) -> Sequence[Module | Tensor | str | sympy.Expr]: + """Convert block to native Pyqtorch representation. + + Args: + block (AbstractBlock): Block to convert. + n_qubits (int, optional): Number of qubits. Defaults to None. + config (Configuration, optional): Backend configuration instance. Defaults to None. + + Raises: + NotImplementedError: For non supported blocks. + + Returns: + Sequence[Module | Tensor | str | sympy.Expr]: Lis of native operations. + """ if isinstance(block, (Tensor, str, sympy.Expr)): # case for hamevo generators if isinstance(block, Tensor): block = block.permute(1, 2, 0) # put batch size in the back @@ -192,7 +204,7 @@ def convert_block( config = Configuration() noise: NoiseHandler | None = None - if apply_noise and hasattr(block, "noise") and block.noise: + if hasattr(block, "noise") and block.noise: noise = convert_digital_noise(block.noise) if isinstance(block, ScaleBlock): From 12ebe267bcc72d53e53391150151323839b669c5 Mon Sep 17 00:00:00 2001 From: Charles MOUSSA Date: Tue, 12 Nov 2024 14:19:56 +0100 Subject: [PATCH 03/22] add new test for expectation of quantum model and digital noise --- qadence/backends/pyqtorch/backend.py | 3 +- qadence/noise/protocols.py | 6 +++- .../qadence/test_noise/test_digital_noise.py | 28 +++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/qadence/backends/pyqtorch/backend.py b/qadence/backends/pyqtorch/backend.py index 4b453c40..fce315d8 100644 --- a/qadence/backends/pyqtorch/backend.py +++ b/qadence/backends/pyqtorch/backend.py @@ -109,7 +109,8 @@ def circuit(self, circuit: QuantumCircuit) -> ConvertedCircuit: if len(passes) > 0: circuit = transpile(*passes)(circuit) # setting noise on blocks - set_noise(circuit, self.config.noise) + if self.config.noise: + set_noise(circuit, self.config.noise) ops = convert_block(circuit.block, n_qubits=circuit.n_qubits, config=self.config) readout_noise = ( diff --git a/qadence/noise/protocols.py b/qadence/noise/protocols.py index 4a42bcff..a8af79ee 100644 --- a/qadence/noise/protocols.py +++ b/qadence/noise/protocols.py @@ -149,7 +149,11 @@ def list(cls) -> list: return list(filter(lambda el: not el.startswith("__"), dir(cls))) def filter(self, protocol: NoiseEnum | str) -> NoiseHandler | None: - is_protocol: list = [p == protocol or isinstance(p, protocol) for p in self.protocol] # type: ignore[arg-type] + is_protocol: list = list() + if protocol == NoiseProtocol.READOUT: + is_protocol = [p == protocol for p in self.protocol] + else: + is_protocol = [isinstance(p, protocol) for p in self.protocol] # type: ignore[arg-type] return ( NoiseHandler( list(compress(self.protocol, is_protocol)), diff --git a/tests/qadence/test_noise/test_digital_noise.py b/tests/qadence/test_noise/test_digital_noise.py index 7c5a9355..60f50363 100644 --- a/tests/qadence/test_noise/test_digital_noise.py +++ b/tests/qadence/test_noise/test_digital_noise.py @@ -92,6 +92,34 @@ def test_run_digital(noisy_config: NoiseProtocol | list[NoiseProtocol]) -> None: assert torch.allclose(noisy_output, native_output) +@pytest.mark.parametrize( + "noisy_config", + [ + NoiseProtocol.DIGITAL.BITFLIP, + [NoiseProtocol.DIGITAL.BITFLIP, NoiseProtocol.DIGITAL.PHASEFLIP], + ], +) +def test_expectation_digital_noise(noisy_config: NoiseProtocol | list[NoiseProtocol]) -> None: + block = kron(H(0), Z(1)) + circuit = QuantumCircuit(2, block) + observable = hamiltonian_factory(circuit.n_qubits, detuning=Z) + noise = NoiseHandler(noisy_config, {"error_probability": 0.1}) + + # Construct a quantum model. + model = QuantumModel(circuit=circuit, observable=observable) + noiseless_expectation = model.expectation(values={}) + + noisy_model = QuantumModel(circuit=circuit, observable=observable, noise=noise) + noisy_expectation = noisy_model.expectation(values={}) + assert not torch.allclose(noiseless_expectation, noisy_expectation) + + backend = backend_factory(backend=BackendName.PYQTORCH, diff_mode=DiffMode.AD) + (pyqtorch_circ, pyqtorch_obs, embed, params) = backend.convert(circuit, observable) + native_expectation = backend.expectation(pyqtorch_circ, pyqtorch_obs, embed(params, {})) + + assert torch.allclose(noisy_expectation, native_expectation) + + @pytest.mark.parametrize( "noise_config", [ From 508230ee783337452a3fc3090a6ec00f6da68ed1 Mon Sep 17 00:00:00 2001 From: chMoussa Date: Tue, 12 Nov 2024 16:36:10 +0100 Subject: [PATCH 04/22] Update qadence/backends/pyqtorch/backend.py Co-authored-by: RolandMacDoland <9250798+RolandMacDoland@users.noreply.github.com> --- qadence/backends/pyqtorch/backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qadence/backends/pyqtorch/backend.py b/qadence/backends/pyqtorch/backend.py index fce315d8..3d9c6b78 100644 --- a/qadence/backends/pyqtorch/backend.py +++ b/qadence/backends/pyqtorch/backend.py @@ -70,7 +70,7 @@ def converted_circuit_with_noise( new_convcirc.abstract.block, n_qubits=new_convcirc.abstract.n_qubits, config=config ) readout = new_convcirc.native.readout_noise - if new_convcirc.native.readout_noise is None: + if readout is None: readout = convert_readout_noise(new_convcirc.abstract.n_qubits, noise_combination) native = pyq.QuantumCircuit( new_convcirc.abstract.n_qubits, From 5d070b8bfb0544ea65047d3bb60f5e97453b0cf1 Mon Sep 17 00:00:00 2001 From: chMoussa Date: Tue, 12 Nov 2024 16:36:37 +0100 Subject: [PATCH 05/22] Update qadence/backends/pyqtorch/backend.py Co-authored-by: RolandMacDoland <9250798+RolandMacDoland@users.noreply.github.com> --- qadence/backends/pyqtorch/backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qadence/backends/pyqtorch/backend.py b/qadence/backends/pyqtorch/backend.py index 3d9c6b78..1670a65d 100644 --- a/qadence/backends/pyqtorch/backend.py +++ b/qadence/backends/pyqtorch/backend.py @@ -52,7 +52,7 @@ def converted_circuit_with_noise( noise (NoiseHandler | None): Noise to add. Returns: - ConvertedCircuit: A new ConvertedCircuit with noise. + ConvertedCircuit: Noisy ConvertedCircuit. """ if noise: From 320283850b5dcaf98678628e054a08430cbf1eed Mon Sep 17 00:00:00 2001 From: Charles MOUSSA Date: Tue, 12 Nov 2024 17:13:43 +0100 Subject: [PATCH 06/22] fix typo --- qadence/backends/pyqtorch/convert_ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qadence/backends/pyqtorch/convert_ops.py b/qadence/backends/pyqtorch/convert_ops.py index caffc1bb..099da813 100644 --- a/qadence/backends/pyqtorch/convert_ops.py +++ b/qadence/backends/pyqtorch/convert_ops.py @@ -190,7 +190,7 @@ def convert_block( NotImplementedError: For non supported blocks. Returns: - Sequence[Module | Tensor | str | sympy.Expr]: Lis of native operations. + Sequence[Module | Tensor | str | sympy.Expr]: List of native operations. """ if isinstance(block, (Tensor, str, sympy.Expr)): # case for hamevo generators if isinstance(block, Tensor): From 626094505d4a33befde223e69dbfe073d821fb87 Mon Sep 17 00:00:00 2001 From: Charles MOUSSA Date: Tue, 12 Nov 2024 17:19:36 +0100 Subject: [PATCH 07/22] more comment --- qadence/backends/pyqtorch/backend.py | 1 + qadence/noise/protocols.py | 9 ++++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/qadence/backends/pyqtorch/backend.py b/qadence/backends/pyqtorch/backend.py index 1670a65d..6378d365 100644 --- a/qadence/backends/pyqtorch/backend.py +++ b/qadence/backends/pyqtorch/backend.py @@ -137,6 +137,7 @@ def observable(self, observable: AbstractBlock, n_qubits: int) -> ConvertedObser ] block = transpile(*transpilations)(observable) # type: ignore[call-overload] # we do not set noise on the observable blocks + # as this would not be correct when computing expectations operations = convert_block(block, n_qubits, self.config) native = pyq.Observable(operations=operations) return ConvertedObservable(native=native, abstract=block, original=observable) diff --git a/qadence/noise/protocols.py b/qadence/noise/protocols.py index a8af79ee..af74e01e 100644 --- a/qadence/noise/protocols.py +++ b/qadence/noise/protocols.py @@ -154,14 +154,13 @@ def filter(self, protocol: NoiseEnum | str) -> NoiseHandler | None: is_protocol = [p == protocol for p in self.protocol] else: is_protocol = [isinstance(p, protocol) for p in self.protocol] # type: ignore[arg-type] - return ( - NoiseHandler( + # if we have at least a match + if sum(is_protocol) > 0: + return NoiseHandler( list(compress(self.protocol, is_protocol)), list(compress(self.options, is_protocol)), ) - if len(is_protocol) > 0 and sum(is_protocol) > 0 - else None - ) + return None def bitflip(self, *args: Any, **kwargs: Any) -> NoiseHandler: self.append(NoiseHandler(NoiseProtocol.DIGITAL.BITFLIP, *args, **kwargs)) From 1c0d45021c3eaa78eb5a9db6ddba60eabaa9735a Mon Sep 17 00:00:00 2001 From: Charles MOUSSA Date: Wed, 13 Nov 2024 09:24:52 +0100 Subject: [PATCH 08/22] testing passing noise backend --- qadence/backends/pyqtorch/backend.py | 4 +--- tests/qadence/test_noise/test_digital_noise.py | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/qadence/backends/pyqtorch/backend.py b/qadence/backends/pyqtorch/backend.py index 6378d365..e303533f 100644 --- a/qadence/backends/pyqtorch/backend.py +++ b/qadence/backends/pyqtorch/backend.py @@ -108,7 +108,7 @@ def circuit(self, circuit: QuantumCircuit) -> ConvertedCircuit: original_circ = circuit if len(passes) > 0: circuit = transpile(*passes)(circuit) - # setting noise on blocks + # Setting noise in the circuit. if self.config.noise: set_noise(circuit, self.config.noise) @@ -136,8 +136,6 @@ def observable(self, observable: AbstractBlock, n_qubits: int) -> ConvertedObser scale_primitive_blocks_only, ] block = transpile(*transpilations)(observable) # type: ignore[call-overload] - # we do not set noise on the observable blocks - # as this would not be correct when computing expectations operations = convert_block(block, n_qubits, self.config) native = pyq.Observable(operations=operations) return ConvertedObservable(native=native, abstract=block, original=observable) diff --git a/tests/qadence/test_noise/test_digital_noise.py b/tests/qadence/test_noise/test_digital_noise.py index 60f50363..d65409c7 100644 --- a/tests/qadence/test_noise/test_digital_noise.py +++ b/tests/qadence/test_noise/test_digital_noise.py @@ -104,20 +104,28 @@ def test_expectation_digital_noise(noisy_config: NoiseProtocol | list[NoiseProto circuit = QuantumCircuit(2, block) observable = hamiltonian_factory(circuit.n_qubits, detuning=Z) noise = NoiseHandler(noisy_config, {"error_probability": 0.1}) + backend = backend_factory(backend=BackendName.PYQTORCH, diff_mode=DiffMode.AD) # Construct a quantum model. model = QuantumModel(circuit=circuit, observable=observable) noiseless_expectation = model.expectation(values={}) + (pyqtorch_circ, pyqtorch_obs, embed, params) = backend.convert(circuit, observable) + native_noisy_expectation = backend.expectation( + pyqtorch_circ, pyqtorch_obs, embed(params, {}), noise=noise + ) + assert not torch.allclose(noiseless_expectation, native_noisy_expectation) + noisy_model = QuantumModel(circuit=circuit, observable=observable, noise=noise) - noisy_expectation = noisy_model.expectation(values={}) - assert not torch.allclose(noiseless_expectation, noisy_expectation) + noisy_model_expectation = noisy_model.expectation(values={}) + assert torch.allclose(noisy_model_expectation, native_noisy_expectation) - backend = backend_factory(backend=BackendName.PYQTORCH, diff_mode=DiffMode.AD) (pyqtorch_circ, pyqtorch_obs, embed, params) = backend.convert(circuit, observable) - native_expectation = backend.expectation(pyqtorch_circ, pyqtorch_obs, embed(params, {})) + noisy_converted_model_expectation = backend.expectation( + pyqtorch_circ, pyqtorch_obs, embed(params, {}) + ) - assert torch.allclose(noisy_expectation, native_expectation) + assert torch.allclose(noisy_converted_model_expectation, native_noisy_expectation) @pytest.mark.parametrize( From 0a8f059b9f8ef7f70e26084a8fe56fd9e869b5a9 Mon Sep 17 00:00:00 2001 From: Charles MOUSSA Date: Wed, 13 Nov 2024 09:28:08 +0100 Subject: [PATCH 09/22] docstring converted_circuit_with_noise --- qadence/backends/pyqtorch/backend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qadence/backends/pyqtorch/backend.py b/qadence/backends/pyqtorch/backend.py index e303533f..966909ea 100644 --- a/qadence/backends/pyqtorch/backend.py +++ b/qadence/backends/pyqtorch/backend.py @@ -48,7 +48,7 @@ def converted_circuit_with_noise( this may raise an error. Args: - circuit (ConvertedCircuit): Original ConvertedCircuit. + circuit (ConvertedCircuit): Input ConvertedCircuit (usually noiseless). noise (NoiseHandler | None): Noise to add. Returns: From 93ddb17badcad433b0c288d027521bdcb6e97554 Mon Sep 17 00:00:00 2001 From: Charles MOUSSA Date: Wed, 13 Nov 2024 11:09:40 +0100 Subject: [PATCH 10/22] separate set_noise functions --- qadence/backends/pyqtorch/backend.py | 83 +++++++++++++++------------- qadence/transpile/noise.py | 13 +++-- 2 files changed, 52 insertions(+), 44 deletions(-) diff --git a/qadence/backends/pyqtorch/backend.py b/qadence/backends/pyqtorch/backend.py index 966909ea..94e0b647 100644 --- a/qadence/backends/pyqtorch/backend.py +++ b/qadence/backends/pyqtorch/backend.py @@ -39,49 +39,43 @@ logger = getLogger(__name__) -def converted_circuit_with_noise( - circuit: ConvertedCircuit, noise: NoiseHandler | None, config: Configuration -) -> ConvertedCircuit: - """For backend functions, get a ConvertedCircuit with noise. - - Note that if config.noise is not None, we try to append to noise and - this may raise an error. +def set_noise_abstract_to_native(circuit: ConvertedCircuit, config: Configuration) -> None: + """Set noise in native blocks from the abstract ones with noise. Args: - circuit (ConvertedCircuit): Input ConvertedCircuit (usually noiseless). - noise (NoiseHandler | None): Noise to add. + circuit (ConvertedCircuit): Input converted circuit. + """ + ops = convert_block(circuit.abstract.block, n_qubits=circuit.native.n_qubits, config=config) + circuit.native = pyq.QuantumCircuit(circuit.native.n_qubits, ops, circuit.native.readout_noise) + + +def set_readout_noise(circuit: ConvertedCircuit, noise: NoiseHandler) -> None: + """Set readout noise in place in native. - Returns: - ConvertedCircuit: Noisy ConvertedCircuit. + Args: + circuit (ConvertedCircuit): Input converted circuit. + noise (NoiseHandler | None): Noise. """ + readout = convert_readout_noise(circuit.abstract.n_qubits, noise) + circuit.native.readout_noise = readout - if noise: - noise_combination = noise - if config.noise: - try: - noise_combination.append(config.noise) - except ValueError as e: - raise ValueError(f"Cannot append provided noise with the config noise. Error: {e}") - new_convcirc = ConvertedCircuit(circuit.native, circuit.abstract, circuit.original) - set_noise(new_convcirc.abstract, noise_combination) - - # the config should not add the noise - ops = convert_block( - new_convcirc.abstract.block, n_qubits=new_convcirc.abstract.n_qubits, config=config - ) - readout = new_convcirc.native.readout_noise - if readout is None: - readout = convert_readout_noise(new_convcirc.abstract.n_qubits, noise_combination) - native = pyq.QuantumCircuit( - new_convcirc.abstract.n_qubits, - ops, - readout, - ) - new_convcirc.native = native - return new_convcirc +def set_block_and_readout_noises( + circuit: ConvertedCircuit, noise: NoiseHandler | None, config: Configuration +) -> None: + """Add noise on blocks and readout on circuit. + + We first start by adding noise to the abstract blocks. Then we do a conversion to their + native representation. Finally, we add readout. - return circuit + Args: + circuit (ConvertedCircuit): Input circuit. + noise (NoiseHandler | None): Noise to add. + """ + if noise: + set_noise(circuit, noise) + set_noise_abstract_to_native(circuit, config) + set_readout_noise(circuit, noise) @dataclass(frozen=True, eq=True) @@ -101,6 +95,17 @@ class Backend(BackendInterface): logger.debug("Initialised") def circuit(self, circuit: QuantumCircuit) -> ConvertedCircuit: + """Return the converted circuit. + + Note that to get a representation with noise, noise + should be passed within the config. + + Args: + circuit (QuantumCircuit): Original circuit + + Returns: + ConvertedCircuit: ConvertedCircuit instance for backend. + """ passes = self.config.transpilation_passes if passes is None: passes = default_passes(self.config) @@ -173,7 +178,7 @@ def _batched_expectation( noise: NoiseHandler | None = None, endianness: Endianness = Endianness.BIG, ) -> Tensor: - circuit = converted_circuit_with_noise(circuit, noise, self.config) + set_block_and_readout_noises(circuit, noise, self.config) state = self.run( circuit, param_values=param_values, @@ -211,7 +216,7 @@ def _looped_expectation( "Define your initial state with `batch_size=1`" ) - circuit = converted_circuit_with_noise(circuit, noise, self.config) + set_block_and_readout_noises(circuit, noise, self.config) list_expvals = [] observables = observable if isinstance(observable, list) else [observable] @@ -267,7 +272,7 @@ def sample( elif state is not None and pyqify_state: n_qubits = circuit.abstract.n_qubits state = pyqify(state, n_qubits) if pyqify_state else state - circuit = converted_circuit_with_noise(circuit, noise, self.config) + set_block_and_readout_noises(circuit, noise, self.config) samples: list[Counter] = circuit.native.sample( state=state, values=param_values, n_shots=n_shots ) diff --git a/qadence/transpile/noise.py b/qadence/transpile/noise.py index 0a0c51ff..64210826 100644 --- a/qadence/transpile/noise.py +++ b/qadence/transpile/noise.py @@ -1,5 +1,6 @@ from __future__ import annotations +from qadence.backend import ConvertedCircuit from qadence.blocks.abstract import AbstractBlock from qadence.circuit import QuantumCircuit from qadence.noise.protocols import NoiseHandler @@ -23,13 +24,15 @@ def _set_noise( def set_noise( - circuit: QuantumCircuit | AbstractBlock, + circuit: QuantumCircuit | AbstractBlock | ConvertedCircuit, noise: NoiseHandler | None, target_class: AbstractBlock | None = None, ) -> QuantumCircuit | AbstractBlock: """ Parses a `QuantumCircuit` or `CompositeBlock` to add noise to specific gates. + If `circuit` is a `ConvertedCircuit`, this is done within `circuit.abstract`. + Changes the input in place. Arguments: @@ -37,10 +40,10 @@ def set_noise( noise: the NoiseHandler protocol to change to, or `None` to remove the noise. target_class: optional class to selectively add noise to. """ - is_circuit_input = isinstance(circuit, QuantumCircuit) - - input_block: AbstractBlock = circuit.block if is_circuit_input else circuit # type: ignore + to_convert = circuit.abstract if isinstance(circuit, ConvertedCircuit) else circuit + is_circuit_input = isinstance(to_convert, QuantumCircuit) - output_block = apply_fn_to_blocks(input_block, _set_noise, noise, target_class) + input_block: AbstractBlock = to_convert.block if is_circuit_input else to_convert # type: ignore + apply_fn_to_blocks(input_block, _set_noise, noise, target_class) return circuit From ec4f61473b1aacebef60559fe19531f8e2f88307 Mon Sep 17 00:00:00 2001 From: Charles MOUSSA Date: Wed, 13 Nov 2024 11:14:19 +0100 Subject: [PATCH 11/22] add if readout --- qadence/backends/pyqtorch/backend.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qadence/backends/pyqtorch/backend.py b/qadence/backends/pyqtorch/backend.py index 94e0b647..529c2c6a 100644 --- a/qadence/backends/pyqtorch/backend.py +++ b/qadence/backends/pyqtorch/backend.py @@ -57,7 +57,8 @@ def set_readout_noise(circuit: ConvertedCircuit, noise: NoiseHandler) -> None: noise (NoiseHandler | None): Noise. """ readout = convert_readout_noise(circuit.abstract.n_qubits, noise) - circuit.native.readout_noise = readout + if readout: + circuit.native.readout_noise = readout def set_block_and_readout_noises( From 0e49465e3c142bf3c8d0fd121808dc0f2a6a19c9 Mon Sep 17 00:00:00 2001 From: Charles MOUSSA Date: Thu, 21 Nov 2024 08:43:16 +0100 Subject: [PATCH 12/22] use filter function in noise filter --- qadence/noise/protocols.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/qadence/noise/protocols.py b/qadence/noise/protocols.py index af74e01e..fdb0f46d 100644 --- a/qadence/noise/protocols.py +++ b/qadence/noise/protocols.py @@ -149,16 +149,22 @@ def list(cls) -> list: return list(filter(lambda el: not el.startswith("__"), dir(cls))) def filter(self, protocol: NoiseEnum | str) -> NoiseHandler | None: - is_protocol: list = list() if protocol == NoiseProtocol.READOUT: - is_protocol = [p == protocol for p in self.protocol] + + def filter_fn(p: NoiseEnum | str) -> bool: + return p == protocol + else: - is_protocol = [isinstance(p, protocol) for p in self.protocol] # type: ignore[arg-type] + + def filter_fn(p: NoiseEnum | str) -> bool: + return isinstance(p, protocol) # type: ignore[arg-type] + + protocol_matches: list = list(filter(filter_fn, self.protocol)) # if we have at least a match - if sum(is_protocol) > 0: + if True in protocol_matches: return NoiseHandler( - list(compress(self.protocol, is_protocol)), - list(compress(self.options, is_protocol)), + list(compress(self.protocol, protocol_matches)), + list(compress(self.options, protocol_matches)), ) return None From 5e5735e9381f4f298ef52f771d38fe39b3c44f94 Mon Sep 17 00:00:00 2001 From: Charles MOUSSA Date: Thu, 21 Nov 2024 08:52:03 +0100 Subject: [PATCH 13/22] set_noise with if elif else --- qadence/transpile/noise.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/qadence/transpile/noise.py b/qadence/transpile/noise.py index 64210826..1f5127df 100644 --- a/qadence/transpile/noise.py +++ b/qadence/transpile/noise.py @@ -40,10 +40,13 @@ def set_noise( noise: the NoiseHandler protocol to change to, or `None` to remove the noise. target_class: optional class to selectively add noise to. """ - to_convert = circuit.abstract if isinstance(circuit, ConvertedCircuit) else circuit - is_circuit_input = isinstance(to_convert, QuantumCircuit) + if isinstance(circuit, ConvertedCircuit): + input_block: AbstractBlock = circuit.abstract.block + elif isinstance(circuit, QuantumCircuit): + input_block = circuit.block + else: + input_block = circuit - input_block: AbstractBlock = to_convert.block if is_circuit_input else to_convert # type: ignore apply_fn_to_blocks(input_block, _set_noise, noise, target_class) return circuit From b1127ec4c009bcfacc0a439c30df5c0b73d0b012 Mon Sep 17 00:00:00 2001 From: Charles MOUSSA Date: Thu, 21 Nov 2024 09:06:55 +0100 Subject: [PATCH 14/22] rm filter --- qadence/noise/protocols.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/qadence/noise/protocols.py b/qadence/noise/protocols.py index fdb0f46d..acca97cb 100644 --- a/qadence/noise/protocols.py +++ b/qadence/noise/protocols.py @@ -149,17 +149,12 @@ def list(cls) -> list: return list(filter(lambda el: not el.startswith("__"), dir(cls))) def filter(self, protocol: NoiseEnum | str) -> NoiseHandler | None: + protocol_matches: list = list() if protocol == NoiseProtocol.READOUT: - - def filter_fn(p: NoiseEnum | str) -> bool: - return p == protocol - + protocol_matches = [p == protocol for p in self.protocol] else: + protocol_matches = [isinstance(p, protocol) for p in self.protocol] # type: ignore[arg-type] - def filter_fn(p: NoiseEnum | str) -> bool: - return isinstance(p, protocol) # type: ignore[arg-type] - - protocol_matches: list = list(filter(filter_fn, self.protocol)) # if we have at least a match if True in protocol_matches: return NoiseHandler( From 834f88e5d8fa3b9f988df39fd6d09b508604b240 Mon Sep 17 00:00:00 2001 From: Charles MOUSSA Date: Thu, 21 Nov 2024 16:33:17 +0100 Subject: [PATCH 15/22] add type readoutnoise for correlated readout --- docs/tutorials/realistic_sims/noise.md | 25 ++++++++++++++++++------ qadence/backends/pyqtorch/convert_ops.py | 6 +++++- qadence/noise/protocols.py | 16 +++++++-------- qadence/types.py | 13 ++++++++++-- tests/qadence/test_noise/test_readout.py | 12 ++++++------ 5 files changed, 49 insertions(+), 23 deletions(-) diff --git a/docs/tutorials/realistic_sims/noise.md b/docs/tutorials/realistic_sims/noise.md index 877a2e6e..09fa0778 100644 --- a/docs/tutorials/realistic_sims/noise.md +++ b/docs/tutorials/realistic_sims/noise.md @@ -13,7 +13,7 @@ from qadence.types import NoiseProtocol analog_noise = NoiseHandler(protocol=NoiseProtocol.ANALOG.DEPOLARIZING, options={"noise_probs": 0.1}) digital_noise = NoiseHandler(protocol=NoiseProtocol.DIGITAL.DEPOLARIZING, options={"error_probability": 0.1}) -readout_noise = NoiseHandler(protocol=NoiseProtocol.READOUT, options={"error_probability": 0.1, "seed": 0}) +readout_noise = NoiseHandler(protocol=NoiseProtocol.READOUT.INDEPENDENTREADOUT, options={"error_probability": 0.1, "seed": 0}) ``` One can also define a `NoiseHandler` passing a list of protocols and a list of options (careful with the order): @@ -36,7 +36,7 @@ from qadence import NoiseHandler from qadence.types import NoiseProtocol depo_noise = NoiseHandler(protocol=NoiseProtocol.DIGITAL.DEPOLARIZING, options={"error_probability": 0.1}) -readout_noise = NoiseHandler(protocol=NoiseProtocol.READOUT, options={"error_probability": 0.1, "seed": 0}) +readout_noise = NoiseHandler(protocol=NoiseProtocol.READOUT.INDEPENDENTREADOUT, options={"error_probability": 0.1, "seed": 0}) noise_combination = NoiseHandler(protocol=NoiseProtocol.DIGITAL.BITFLIP, options={"error_probability": 0.1}) noise_combination.append([depo_noise, readout_noise]) @@ -65,6 +65,10 @@ $$ T(x|x')=\delta_{xx'} $$ +Two types of readout protocols are available: +- `NoiseProtocol.READOUT.INDEPENDENTREADOUT` where each bit can be corrupted independently of each other. +- `NoiseProtocol.READOUT.CORRELATEDREADOUT` where we can define of confusion matrix of corruption between each +possible bitstrings. Qadence offers to simulate readout errors with the `NoiseHandler` to corrupt the output samples of a simulation, through execution via a `QuantumModel`: @@ -82,7 +86,7 @@ observable = hamiltonian_factory(circuit.n_qubits, detuning=Z) model = QuantumModel(circuit=circuit, observable=observable) # Define a noise model to use. -noise = NoiseHandler(protocol=NoiseProtocol.READOUT) +noise = NoiseHandler(protocol=NoiseProtocol.READOUT.INDEPENDENTREADOUT) # Run noiseless and noisy simulations. noiseless_samples = model.sample(n_shots=100) @@ -93,12 +97,21 @@ print(f"noisy = {noisy_samples}") # markdown-exec: hide ``` It is possible to pass options to the noise model. In the previous example, a noise matrix is implicitly computed from a -uniform distribution. The `option` dictionary argument accepts the following options: +uniform distribution. + +For `NoiseProtocol.READOUT.INDEPENDENTREADOUT`, the `option` dictionary argument accepts the following options: - `seed`: defaulted to `None`, for reproducibility purposes -- `error_probability`: defaulted to 0.1, a bit flip probability +- `error_probability`: If float, the same probability is applied to every bit. By default, this is 0.1. + If a 1D tensor with the number of elements equal to the number of qubits, a different probability can be set for each qubit. If a tensor of shape (n_qubits, 2, 2) is passed, that is a confusion matrix, we extract the error_probability. + and do not compute internally the confusion matrix as in the other cases. - `noise_distribution`: defaulted to `WhiteNoise.UNIFORM`, for non-uniform noise distributions +For `NoiseProtocol.READOUT.CORRELATEDREADOUT`, the `option` dictionary argument accepts the following options: +- `confusion_matrix`: The square matrix representing $T(x|x')$ for each possible bitstring of length `n` qubits. Should be of size (2**n, 2**n). +- `seed`: defaulted to `None`, for reproducibility purposes + + Noisy simulations go hand-in-hand with measurement protocols discussed in the previous [section](measurements.md), to assess the impact of noise on expectation values. In this case, both measurement and noise protocols have to be defined appropriately. Please note that a noise protocol without a measurement protocol will be ignored for expectation values computations. @@ -107,7 +120,7 @@ from qadence.measurements import Measurements # Define a noise model with options. options = {"error_probability": 0.01} -noise = NoiseHandler(protocol=NoiseProtocol.READOUT, options=options) +noise = NoiseHandler(protocol=NoiseProtocol.READOUT.INDEPENDENTREADOUT, options=options) # Define a tomographical measurement protocol with options. options = {"n_shots": 10000} diff --git a/qadence/backends/pyqtorch/convert_ops.py b/qadence/backends/pyqtorch/convert_ops.py index 099da813..e6fbfe70 100644 --- a/qadence/backends/pyqtorch/convert_ops.py +++ b/qadence/backends/pyqtorch/convert_ops.py @@ -373,4 +373,8 @@ def convert_readout_noise(n_qubits: int, noise: NoiseHandler) -> pyq.noise.Reado readout_part = noise.filter(NoiseProtocol.READOUT) if readout_part is None: return None - return pyq.noise.ReadoutNoise(n_qubits, **readout_part.options[0]) + + if readout_part.protocol[0] == NoiseProtocol.READOUT.INDEPENDENTREADOUT: + return pyq.noise.ReadoutNoise(n_qubits, **readout_part.options[0]) + else: + return pyq.noise.CorrelatedReadoutNoise(**readout_part.options[0]) diff --git a/qadence/noise/protocols.py b/qadence/noise/protocols.py index acca97cb..5700a22a 100644 --- a/qadence/noise/protocols.py +++ b/qadence/noise/protocols.py @@ -148,12 +148,8 @@ def _from_dict(cls, d: dict | None) -> NoiseHandler | None: def list(cls) -> list: return list(filter(lambda el: not el.startswith("__"), dir(cls))) - def filter(self, protocol: NoiseEnum | str) -> NoiseHandler | None: - protocol_matches: list = list() - if protocol == NoiseProtocol.READOUT: - protocol_matches = [p == protocol for p in self.protocol] - else: - protocol_matches = [isinstance(p, protocol) for p in self.protocol] # type: ignore[arg-type] + def filter(self, protocol: NoiseEnum) -> NoiseHandler | None: + protocol_matches: list = [isinstance(p, protocol) for p in self.protocol] # type: ignore[arg-type] # if we have at least a match if True in protocol_matches: @@ -201,6 +197,10 @@ def dephasing(self, *args: Any, **kwargs: Any) -> NoiseHandler: self.append(NoiseHandler(NoiseProtocol.ANALOG.DEPHASING, *args, **kwargs)) return self - def readout(self, *args: Any, **kwargs: Any) -> NoiseHandler: - self.append(NoiseHandler(NoiseProtocol.READOUT, *args, **kwargs)) + def independentreadout(self, *args: Any, **kwargs: Any) -> NoiseHandler: + self.append(NoiseHandler(NoiseProtocol.READOUT.INDEPENDENTREADOUT, *args, **kwargs)) + return self + + def correlated_readout(self, *args: Any, **kwargs: Any) -> NoiseHandler: + self.append(NoiseHandler(NoiseProtocol.READOUT.CORRELATEDREADOUT, *args, **kwargs)) return self diff --git a/qadence/types.py b/qadence/types.py index 501dd9d0..6e06c147 100644 --- a/qadence/types.py +++ b/qadence/types.py @@ -470,16 +470,25 @@ class AnalogNoise(StrEnum): DEPHASING = "Dephasing" +class ReadoutNoise(StrEnum): + """Type of readout protocol.""" + + INDEPENDENTREADOUT = "IndependentReadout" + """Simple readout protocols where each qubit is corrupted independently.""" + CORRELATEDREADOUT = "CorrelatedReadout" + """Using a confusion matrix (2**n, 2**n) for corrupting bitstrings values.""" + + @dataclass class NoiseProtocol: """Type of noise protocol.""" ANALOG = AnalogNoise """Noise applied in analog blocks.""" - READOUT = "Readout" + READOUT = ReadoutNoise """Noise applied on outputs of quantum programs.""" DIGITAL = DigitalNoise """Noise applied to digital blocks.""" -NoiseEnum = Union[DigitalNoise, AnalogNoise, str] +NoiseEnum = Union[DigitalNoise, AnalogNoise, ReadoutNoise] diff --git a/tests/qadence/test_noise/test_readout.py b/tests/qadence/test_noise/test_readout.py index ed33f873..7f1146b4 100644 --- a/tests/qadence/test_noise/test_readout.py +++ b/tests/qadence/test_noise/test_readout.py @@ -65,7 +65,7 @@ def test_readout_error_quantum_model( noisy_samples: list[Counter] = QuantumModel( QuantumCircuit(block.n_qubits, block), backend=backend, diff_mode=diff_mode - ).sample(noise=NoiseHandler(protocol=NoiseProtocol.READOUT), n_shots=n_shots) + ).sample(noise=NoiseHandler(protocol=NoiseProtocol.READOUT.INDEPENDENTREADOUT), n_shots=n_shots) for noiseless, noisy in zip(noiseless_samples, noisy_samples): assert sum(noiseless.values()) == sum(noisy.values()) == n_shots @@ -88,7 +88,7 @@ def test_readout_error_backends(backend: BackendName) -> None: samples = qd.sample(feature_map, n_shots=1000, values=inputs, backend=backend, noise=None) # introduce noise options = {"error_probability": error_probability} - noise = NoiseHandler(protocol=NoiseProtocol.READOUT, options=options) + noise = NoiseHandler(protocol=NoiseProtocol.READOUT.INDEPENDENTREADOUT, options=options) noisy_samples = qd.sample( feature_map, n_shots=1000, values=inputs, backend=backend, noise=noise ) @@ -120,7 +120,7 @@ def test_readout_error_with_measurements( observable = hamiltonian_factory(circuit.n_qubits, detuning=Z) model = QuantumModel(circuit=circuit, observable=observable, diff_mode=DiffMode.GPSR) - noise = NoiseHandler(protocol=NoiseProtocol.READOUT) + noise = NoiseHandler(protocol=NoiseProtocol.READOUT.INDEPENDENTREADOUT) measurement = Measurements(protocol=str(measurement_proto), options=options) noisy = model.expectation(values=inputs, measurement=measurement, noise=noise) @@ -137,7 +137,7 @@ def test_readout_error_with_measurements( def test_serialization() -> None: - noise = NoiseHandler(protocol=NoiseProtocol.READOUT) + noise = NoiseHandler(protocol=NoiseProtocol.READOUT.INDEPENDENTREADOUT) serialized_noise = NoiseHandler._from_dict(noise._to_dict()) assert noise == serialized_noise @@ -151,9 +151,9 @@ def test_serialization() -> None: ], ) def test_append(noise_config: NoiseProtocol | list[NoiseProtocol]) -> None: - noise = NoiseHandler(protocol=NoiseProtocol.READOUT) + noise = NoiseHandler(protocol=NoiseProtocol.READOUT.INDEPENDENTREADOUT) options = {"error_probability": 0.1} with pytest.raises(ValueError): noise.append(NoiseHandler(noise_config, options)) with pytest.raises(ValueError): - noise.readout(options) + noise.independentreadout(options) From 52d303061e180da5ae665524e03c7bbfb0e21dcd Mon Sep 17 00:00:00 2001 From: Charles MOUSSA Date: Thu, 21 Nov 2024 16:44:41 +0100 Subject: [PATCH 16/22] add test serialization --- docs/tutorials/realistic_sims/noise.md | 2 +- tests/qadence/test_noise/test_readout.py | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/tutorials/realistic_sims/noise.md b/docs/tutorials/realistic_sims/noise.md index 09fa0778..f75da8ce 100644 --- a/docs/tutorials/realistic_sims/noise.md +++ b/docs/tutorials/realistic_sims/noise.md @@ -103,7 +103,7 @@ For `NoiseProtocol.READOUT.INDEPENDENTREADOUT`, the `option` dictionary argument - `seed`: defaulted to `None`, for reproducibility purposes - `error_probability`: If float, the same probability is applied to every bit. By default, this is 0.1. - If a 1D tensor with the number of elements equal to the number of qubits, a different probability can be set for each qubit. If a tensor of shape (n_qubits, 2, 2) is passed, that is a confusion matrix, we extract the error_probability. + If a 1D tensor with the number of elements equal to the number of qubits, a different probability can be set for each qubit. If a tensor of shape (n_qubits, 2, 2) is passed, that is a confusion matrix obtained from experiments, we extract the error_probability. and do not compute internally the confusion matrix as in the other cases. - `noise_distribution`: defaulted to `WhiteNoise.UNIFORM`, for non-uniform noise distributions diff --git a/tests/qadence/test_noise/test_readout.py b/tests/qadence/test_noise/test_readout.py index 7f1146b4..be4e1ff3 100644 --- a/tests/qadence/test_noise/test_readout.py +++ b/tests/qadence/test_noise/test_readout.py @@ -141,6 +141,15 @@ def test_serialization() -> None: serialized_noise = NoiseHandler._from_dict(noise._to_dict()) assert noise == serialized_noise + rand_confusion = torch.rand(4, 4) + rand_confusion = rand_confusion / rand_confusion.sum(dim=1, keepdim=True) + noise = NoiseHandler( + protocol=NoiseProtocol.READOUT.CORRELATEDREADOUT, + options={"seed": 0, "confusion_matrix": rand_confusion}, + ) + serialized_noise = NoiseHandler._from_dict(noise._to_dict()) + assert noise == serialized_noise + @pytest.mark.parametrize( "noise_config", From bc6d17e61f33c6b57aad2679ddc871e89e860644 Mon Sep 17 00:00:00 2001 From: Charles MOUSSA Date: Thu, 21 Nov 2024 16:59:58 +0100 Subject: [PATCH 17/22] add correlatedreadout in tests --- tests/qadence/test_noise/test_readout.py | 32 +++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/tests/qadence/test_noise/test_readout.py b/tests/qadence/test_noise/test_readout.py index be4e1ff3..4e1472e9 100644 --- a/tests/qadence/test_noise/test_readout.py +++ b/tests/qadence/test_noise/test_readout.py @@ -8,6 +8,7 @@ import qadence as qd from qadence import BackendName, QuantumModel +from qadence.backends.pyqtorch.convert_ops import convert_readout_noise from qadence.blocks import ( AbstractBlock, add, @@ -63,9 +64,13 @@ def test_readout_error_quantum_model( QuantumCircuit(block.n_qubits, block), backend=backend, diff_mode=diff_mode ).sample(n_shots=n_shots) + noise_protocol: NoiseHandler = NoiseHandler( + protocol=NoiseProtocol.READOUT.INDEPENDENTREADOUT, + options={"error_probability": error_probability}, + ) noisy_samples: list[Counter] = QuantumModel( QuantumCircuit(block.n_qubits, block), backend=backend, diff_mode=diff_mode - ).sample(noise=NoiseHandler(protocol=NoiseProtocol.READOUT.INDEPENDENTREADOUT), n_shots=n_shots) + ).sample(noise=noise_protocol, n_shots=n_shots) for noiseless, noisy in zip(noiseless_samples, noisy_samples): assert sum(noiseless.values()) == sum(noisy.values()) == n_shots @@ -76,6 +81,28 @@ def test_readout_error_quantum_model( atol=1e-1, ) + if backend == BackendName.PYQTORCH: + pyqnoise = convert_readout_noise(block.n_qubits, noise_protocol) + if pyqnoise is not None: + if pyqnoise.confusion_matrix is None: + pyqnoise.create_noise_matrix(n_shots) + confusion_mat = pyqnoise.confusion_matrix + correlated_noise_protocol: NoiseHandler = NoiseHandler( + protocol=NoiseProtocol.READOUT.CORRELATEDREADOUT, + options={"confusion_matrix": confusion_mat}, + ) + corr_noisy_samples: list[Counter] = QuantumModel( + QuantumCircuit(block.n_qubits, block), backend=backend, diff_mode=diff_mode + ).sample(noise=correlated_noise_protocol, n_shots=n_shots) + for noiseless, noisy in zip(noiseless_samples, corr_noisy_samples): + assert sum(noiseless.values()) == sum(noisy.values()) == n_shots + assert js_divergence(noiseless, noisy) > 0.0 + assert torch.allclose( + torch.tensor(1.0 - js_divergence(noiseless, noisy)), + torch.ones(1) - error_probability, + atol=1e-1, + ) + @pytest.mark.parametrize("backend", [BackendName.PYQTORCH, BackendName.PULSER]) def test_readout_error_backends(backend: BackendName) -> None: @@ -166,3 +193,6 @@ def test_append(noise_config: NoiseProtocol | list[NoiseProtocol]) -> None: noise.append(NoiseHandler(noise_config, options)) with pytest.raises(ValueError): noise.independentreadout(options) + + with pytest.raises(ValueError): + noise.correlated_readout({"confusion_matrix": torch.rand(4, 4)}) From daf73b63b204ea145ca80aad417754e7586b0b98 Mon Sep 17 00:00:00 2001 From: Charles MOUSSA Date: Thu, 21 Nov 2024 17:05:42 +0100 Subject: [PATCH 18/22] fix isinstance when veryfing noise protocol --- qadence/noise/protocols.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qadence/noise/protocols.py b/qadence/noise/protocols.py index 5700a22a..32dcd388 100644 --- a/qadence/noise/protocols.py +++ b/qadence/noise/protocols.py @@ -53,7 +53,7 @@ def __init__( self.verify_all_protocols() def _verify_single_protocol(self, protocol: NoiseEnum, option: dict) -> None: - if protocol != NoiseProtocol.READOUT: + if not isinstance(protocol, NoiseProtocol.READOUT): # type: ignore[arg-type] name_mandatory_option = ( "noise_probs" if isinstance(protocol, NoiseProtocol.ANALOG) else "error_probability" ) From dd32238fbd2da06fcf5b7bfae40285eabbdad1af Mon Sep 17 00:00:00 2001 From: Charles MOUSSA Date: Thu, 21 Nov 2024 18:18:13 +0100 Subject: [PATCH 19/22] fix test corr readout --- qadence/noise/protocols.py | 6 +- tests/qadence/test_noise/test_readout.py | 97 +++++++++++++----------- 2 files changed, 56 insertions(+), 47 deletions(-) diff --git a/qadence/noise/protocols.py b/qadence/noise/protocols.py index 32dcd388..9e0dea84 100644 --- a/qadence/noise/protocols.py +++ b/qadence/noise/protocols.py @@ -86,10 +86,10 @@ def verify_all_protocols(self) -> None: if types.count(NoiseProtocol.ANALOG) > 1: raise ValueError("Multiple Analog Noises are not supported yet.") - if NoiseProtocol.READOUT in self.protocol: + if NoiseProtocol.READOUT in unique_types: if ( - self.protocol[-1] != NoiseProtocol.READOUT - or self.protocol.count(NoiseProtocol.READOUT) > 1 + not isinstance(self.protocol[-1], NoiseProtocol.READOUT) + or types.count(NoiseProtocol.READOUT) > 1 ): raise ValueError("Only define a NoiseHandler with one READOUT as the last Noise.") diff --git a/tests/qadence/test_noise/test_readout.py b/tests/qadence/test_noise/test_readout.py index 4e1472e9..504eb464 100644 --- a/tests/qadence/test_noise/test_readout.py +++ b/tests/qadence/test_noise/test_readout.py @@ -8,7 +8,6 @@ import qadence as qd from qadence import BackendName, QuantumModel -from qadence.backends.pyqtorch.convert_ops import convert_readout_noise from qadence.blocks import ( AbstractBlock, add, @@ -33,44 +32,57 @@ @pytest.mark.flaky(max_runs=5) @pytest.mark.parametrize( - "error_probability, n_shots, block, backend", + "error_probability, n_shots, block", [ - (0.1, 100, kron(X(0), X(1)), BackendName.PYQTORCH), - (0.1, 200, kron(Z(0), Z(1), Z(2)) + kron(X(0), Y(1), Z(2)), BackendName.PYQTORCH), - (0.01, 1000, add(Z(0), Z(1), Z(2)), BackendName.PYQTORCH), + ( + 0.1, + 100, + kron(X(0), X(1)), + ), + ( + 0.1, + 200, + kron(Z(0), Z(1), Z(2)) + kron(X(0), Y(1), Z(2)), + ), + ( + 0.01, + 1000, + add(Z(0), Z(1), Z(2)), + ), ( 0.1, 2000, HamEvo( generator=kron(X(0), X(1)) + kron(Z(0), Z(1)) + kron(X(2), X(3)), parameter=0.005 ), - BackendName.PYQTORCH, ), - (0.1, 500, add(Z(0), Z(1), kron(X(2), X(3))) + add(X(2), X(3)), BackendName.PYQTORCH), - (0.05, 10000, add(kron(Z(0), Z(1)), kron(X(2), X(3))), BackendName.PYQTORCH), - (0.2, 1000, hamiltonian_factory(4, detuning=Z), BackendName.PYQTORCH), - (0.1, 500, kron(Z(0), Z(1)) + CNOT(0, 1), BackendName.PYQTORCH), + ( + 0.1, + 500, + add(Z(0), Z(1), kron(X(2), X(3))) + add(X(2), X(3)), + ), + (0.05, 10000, add(kron(Z(0), Z(1)), kron(X(2), X(3)))), + (0.2, 1000, hamiltonian_factory(4, detuning=Z)), + (0.1, 500, kron(Z(0), Z(1)) + CNOT(0, 1)), ], ) def test_readout_error_quantum_model( error_probability: float, n_shots: int, block: AbstractBlock, - backend: BackendName, ) -> None: - diff_mode = "ad" if backend == BackendName.PYQTORCH else "gpsr" - - noiseless_samples: list[Counter] = QuantumModel( + backend = BackendName.PYQTORCH + diff_mode = "ad" + model = QuantumModel( QuantumCircuit(block.n_qubits, block), backend=backend, diff_mode=diff_mode - ).sample(n_shots=n_shots) + ) + noiseless_samples: list[Counter] = model.sample(n_shots=n_shots) noise_protocol: NoiseHandler = NoiseHandler( protocol=NoiseProtocol.READOUT.INDEPENDENTREADOUT, options={"error_probability": error_probability}, ) - noisy_samples: list[Counter] = QuantumModel( - QuantumCircuit(block.n_qubits, block), backend=backend, diff_mode=diff_mode - ).sample(noise=noise_protocol, n_shots=n_shots) + noisy_samples: list[Counter] = model.sample(noise=noise_protocol, n_shots=n_shots) for noiseless, noisy in zip(noiseless_samples, noisy_samples): assert sum(noiseless.values()) == sum(noisy.values()) == n_shots @@ -81,27 +93,16 @@ def test_readout_error_quantum_model( atol=1e-1, ) - if backend == BackendName.PYQTORCH: - pyqnoise = convert_readout_noise(block.n_qubits, noise_protocol) - if pyqnoise is not None: - if pyqnoise.confusion_matrix is None: - pyqnoise.create_noise_matrix(n_shots) - confusion_mat = pyqnoise.confusion_matrix - correlated_noise_protocol: NoiseHandler = NoiseHandler( - protocol=NoiseProtocol.READOUT.CORRELATEDREADOUT, - options={"confusion_matrix": confusion_mat}, - ) - corr_noisy_samples: list[Counter] = QuantumModel( - QuantumCircuit(block.n_qubits, block), backend=backend, diff_mode=diff_mode - ).sample(noise=correlated_noise_protocol, n_shots=n_shots) - for noiseless, noisy in zip(noiseless_samples, corr_noisy_samples): - assert sum(noiseless.values()) == sum(noisy.values()) == n_shots - assert js_divergence(noiseless, noisy) > 0.0 - assert torch.allclose( - torch.tensor(1.0 - js_divergence(noiseless, noisy)), - torch.ones(1) - error_probability, - atol=1e-1, - ) + rand_confusion = torch.rand(2**block.n_qubits, 2**block.n_qubits) + rand_confusion = rand_confusion / rand_confusion.sum(dim=1, keepdim=True) + corr_noise_protocol: NoiseHandler = NoiseHandler( + protocol=NoiseProtocol.READOUT.CORRELATEDREADOUT, + options={"confusion_matrix": rand_confusion}, + ) + corr_noisy_samples: list[Counter] = model.sample(noise=corr_noise_protocol, n_shots=n_shots) + for noiseless, noisy in zip(noiseless_samples, corr_noisy_samples): + assert sum(noiseless.values()) == sum(noisy.values()) == n_shots + assert js_divergence(noiseless, noisy) > 0.0 @pytest.mark.parametrize("backend", [BackendName.PYQTORCH, BackendName.PULSER]) @@ -186,13 +187,21 @@ def test_serialization() -> None: [NoiseProtocol.DIGITAL.BITFLIP, NoiseProtocol.DIGITAL.PHASEFLIP], ], ) -def test_append(noise_config: NoiseProtocol | list[NoiseProtocol]) -> None: - noise = NoiseHandler(protocol=NoiseProtocol.READOUT.INDEPENDENTREADOUT) +@pytest.mark.parametrize( + "initial_noise", + [ + NoiseHandler(protocol=NoiseProtocol.READOUT.INDEPENDENTREADOUT), + NoiseHandler(protocol=NoiseProtocol.READOUT.CORRELATEDREADOUT, options=torch.rand((4, 4))), + ], +) +def test_append( + initial_noise: NoiseHandler, noise_config: NoiseProtocol | list[NoiseProtocol] +) -> None: options = {"error_probability": 0.1} with pytest.raises(ValueError): - noise.append(NoiseHandler(noise_config, options)) + initial_noise.append(NoiseHandler(noise_config, options)) with pytest.raises(ValueError): - noise.independentreadout(options) + initial_noise.independentreadout(options) with pytest.raises(ValueError): - noise.correlated_readout({"confusion_matrix": torch.rand(4, 4)}) + initial_noise.correlated_readout({"confusion_matrix": torch.rand(4, 4)}) From 68b969cf6fb6fafdbe1849fa0be48c2b4b1ab083 Mon Sep 17 00:00:00 2001 From: Charles MOUSSA Date: Thu, 21 Nov 2024 18:20:21 +0100 Subject: [PATCH 20/22] improve corr test --- tests/qadence/test_noise/test_readout.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/qadence/test_noise/test_readout.py b/tests/qadence/test_noise/test_readout.py index 504eb464..d0c3e2f5 100644 --- a/tests/qadence/test_noise/test_readout.py +++ b/tests/qadence/test_noise/test_readout.py @@ -99,11 +99,17 @@ def test_readout_error_quantum_model( protocol=NoiseProtocol.READOUT.CORRELATEDREADOUT, options={"confusion_matrix": rand_confusion}, ) + # assert difference with noiseless samples corr_noisy_samples: list[Counter] = model.sample(noise=corr_noise_protocol, n_shots=n_shots) for noiseless, noisy in zip(noiseless_samples, corr_noisy_samples): assert sum(noiseless.values()) == sum(noisy.values()) == n_shots assert js_divergence(noiseless, noisy) > 0.0 + # assert difference noisy samples + for noisy, corr_noisy in zip(noisy_samples, corr_noisy_samples): + assert sum(noisy.values()) == sum(corr_noisy.values()) == n_shots + assert js_divergence(noisy, corr_noisy) > 0.0 + @pytest.mark.parametrize("backend", [BackendName.PYQTORCH, BackendName.PULSER]) def test_readout_error_backends(backend: BackendName) -> None: From dbbfc7e82a1feb2568468d25d3c28acb09328de6 Mon Sep 17 00:00:00 2001 From: Charles MOUSSA Date: Thu, 21 Nov 2024 18:27:32 +0100 Subject: [PATCH 21/22] fix docs --- docs/tutorials/realistic_sims/mitigation.md | 2 +- docs/tutorials/realistic_sims/noise.md | 2 +- qadence/noise/protocols.py | 2 +- tests/qadence/test_noise/test_readout.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/tutorials/realistic_sims/mitigation.md b/docs/tutorials/realistic_sims/mitigation.md index 76ec0982..26edba82 100644 --- a/docs/tutorials/realistic_sims/mitigation.md +++ b/docs/tutorials/realistic_sims/mitigation.md @@ -45,7 +45,7 @@ observable = hamiltonian_factory(circuit.n_qubits, detuning=Z) model = QuantumModel(circuit=circuit, observable=observable) # Define a noise model to use: -noise = NoiseHandler(NoiseProtocol.READOUT) +noise = NoiseHandler(NoiseProtocol.READOUT.INDEPENDENTREADOUT) # Define the mitigation method solving the minimization problem: options={"optimization_type": ReadOutOptimization.CONSTRAINED} # ReadOutOptimization.MLE for the alternative method. mitigation = Mitigations(protocol=Mitigations.READOUT, options=options) diff --git a/docs/tutorials/realistic_sims/noise.md b/docs/tutorials/realistic_sims/noise.md index f75da8ce..43ea446f 100644 --- a/docs/tutorials/realistic_sims/noise.md +++ b/docs/tutorials/realistic_sims/noise.md @@ -49,7 +49,7 @@ Finally, one can add directly a few pre-defined types using several `NoiseHandle from qadence import NoiseHandler from qadence.types import NoiseProtocol noise_combination = NoiseHandler(protocol=NoiseProtocol.DIGITAL.BITFLIP, options={"error_probability": 0.1}) -noise_combination.digital_depolarizing({"error_probability": 0.1}).readout({"error_probability": 0.1, "seed": 0}) +noise_combination.digital_depolarizing({"error_probability": 0.1}).independent_readout({"error_probability": 0.1, "seed": 0}) print(noise_combination) ``` diff --git a/qadence/noise/protocols.py b/qadence/noise/protocols.py index 9e0dea84..6b3f3cdc 100644 --- a/qadence/noise/protocols.py +++ b/qadence/noise/protocols.py @@ -197,7 +197,7 @@ def dephasing(self, *args: Any, **kwargs: Any) -> NoiseHandler: self.append(NoiseHandler(NoiseProtocol.ANALOG.DEPHASING, *args, **kwargs)) return self - def independentreadout(self, *args: Any, **kwargs: Any) -> NoiseHandler: + def independent_readout(self, *args: Any, **kwargs: Any) -> NoiseHandler: self.append(NoiseHandler(NoiseProtocol.READOUT.INDEPENDENTREADOUT, *args, **kwargs)) return self diff --git a/tests/qadence/test_noise/test_readout.py b/tests/qadence/test_noise/test_readout.py index d0c3e2f5..112ccacc 100644 --- a/tests/qadence/test_noise/test_readout.py +++ b/tests/qadence/test_noise/test_readout.py @@ -207,7 +207,7 @@ def test_append( with pytest.raises(ValueError): initial_noise.append(NoiseHandler(noise_config, options)) with pytest.raises(ValueError): - initial_noise.independentreadout(options) + initial_noise.independent_readout(options) with pytest.raises(ValueError): initial_noise.correlated_readout({"confusion_matrix": torch.rand(4, 4)}) From 328ee6348a530c1c51367927b64527f1559a2876 Mon Sep 17 00:00:00 2001 From: Charles MOUSSA Date: Mon, 25 Nov 2024 10:28:23 +0100 Subject: [PATCH 22/22] change nomenclature readout --- docs/tutorials/realistic_sims/mitigation.md | 2 +- docs/tutorials/realistic_sims/noise.md | 18 +++++++++--------- qadence/backends/pyqtorch/convert_ops.py | 2 +- qadence/noise/protocols.py | 8 ++++---- qadence/types.py | 4 ++-- tests/qadence/test_noise/test_readout.py | 20 ++++++++++---------- 6 files changed, 27 insertions(+), 27 deletions(-) diff --git a/docs/tutorials/realistic_sims/mitigation.md b/docs/tutorials/realistic_sims/mitigation.md index 26edba82..bf82b809 100644 --- a/docs/tutorials/realistic_sims/mitigation.md +++ b/docs/tutorials/realistic_sims/mitigation.md @@ -45,7 +45,7 @@ observable = hamiltonian_factory(circuit.n_qubits, detuning=Z) model = QuantumModel(circuit=circuit, observable=observable) # Define a noise model to use: -noise = NoiseHandler(NoiseProtocol.READOUT.INDEPENDENTREADOUT) +noise = NoiseHandler(NoiseProtocol.READOUT.INDEPENDENT) # Define the mitigation method solving the minimization problem: options={"optimization_type": ReadOutOptimization.CONSTRAINED} # ReadOutOptimization.MLE for the alternative method. mitigation = Mitigations(protocol=Mitigations.READOUT, options=options) diff --git a/docs/tutorials/realistic_sims/noise.md b/docs/tutorials/realistic_sims/noise.md index 43ea446f..85ebb635 100644 --- a/docs/tutorials/realistic_sims/noise.md +++ b/docs/tutorials/realistic_sims/noise.md @@ -13,7 +13,7 @@ from qadence.types import NoiseProtocol analog_noise = NoiseHandler(protocol=NoiseProtocol.ANALOG.DEPOLARIZING, options={"noise_probs": 0.1}) digital_noise = NoiseHandler(protocol=NoiseProtocol.DIGITAL.DEPOLARIZING, options={"error_probability": 0.1}) -readout_noise = NoiseHandler(protocol=NoiseProtocol.READOUT.INDEPENDENTREADOUT, options={"error_probability": 0.1, "seed": 0}) +readout_noise = NoiseHandler(protocol=NoiseProtocol.READOUT.INDEPENDENT, options={"error_probability": 0.1, "seed": 0}) ``` One can also define a `NoiseHandler` passing a list of protocols and a list of options (careful with the order): @@ -36,7 +36,7 @@ from qadence import NoiseHandler from qadence.types import NoiseProtocol depo_noise = NoiseHandler(protocol=NoiseProtocol.DIGITAL.DEPOLARIZING, options={"error_probability": 0.1}) -readout_noise = NoiseHandler(protocol=NoiseProtocol.READOUT.INDEPENDENTREADOUT, options={"error_probability": 0.1, "seed": 0}) +readout_noise = NoiseHandler(protocol=NoiseProtocol.READOUT.INDEPENDENT, options={"error_probability": 0.1, "seed": 0}) noise_combination = NoiseHandler(protocol=NoiseProtocol.DIGITAL.BITFLIP, options={"error_probability": 0.1}) noise_combination.append([depo_noise, readout_noise]) @@ -49,7 +49,7 @@ Finally, one can add directly a few pre-defined types using several `NoiseHandle from qadence import NoiseHandler from qadence.types import NoiseProtocol noise_combination = NoiseHandler(protocol=NoiseProtocol.DIGITAL.BITFLIP, options={"error_probability": 0.1}) -noise_combination.digital_depolarizing({"error_probability": 0.1}).independent_readout({"error_probability": 0.1, "seed": 0}) +noise_combination.digital_depolarizing({"error_probability": 0.1}).readout_independent({"error_probability": 0.1, "seed": 0}) print(noise_combination) ``` @@ -66,8 +66,8 @@ T(x|x')=\delta_{xx'} $$ Two types of readout protocols are available: -- `NoiseProtocol.READOUT.INDEPENDENTREADOUT` where each bit can be corrupted independently of each other. -- `NoiseProtocol.READOUT.CORRELATEDREADOUT` where we can define of confusion matrix of corruption between each +- `NoiseProtocol.READOUT.INDEPENDENT` where each bit can be corrupted independently of each other. +- `NoiseProtocol.READOUT.CORRELATED` where we can define of confusion matrix of corruption between each possible bitstrings. Qadence offers to simulate readout errors with the `NoiseHandler` to corrupt the output @@ -86,7 +86,7 @@ observable = hamiltonian_factory(circuit.n_qubits, detuning=Z) model = QuantumModel(circuit=circuit, observable=observable) # Define a noise model to use. -noise = NoiseHandler(protocol=NoiseProtocol.READOUT.INDEPENDENTREADOUT) +noise = NoiseHandler(protocol=NoiseProtocol.READOUT.INDEPENDENT) # Run noiseless and noisy simulations. noiseless_samples = model.sample(n_shots=100) @@ -99,7 +99,7 @@ print(f"noisy = {noisy_samples}") # markdown-exec: hide It is possible to pass options to the noise model. In the previous example, a noise matrix is implicitly computed from a uniform distribution. -For `NoiseProtocol.READOUT.INDEPENDENTREADOUT`, the `option` dictionary argument accepts the following options: +For `NoiseProtocol.READOUT.INDEPENDENT`, the `option` dictionary argument accepts the following options: - `seed`: defaulted to `None`, for reproducibility purposes - `error_probability`: If float, the same probability is applied to every bit. By default, this is 0.1. @@ -107,7 +107,7 @@ For `NoiseProtocol.READOUT.INDEPENDENTREADOUT`, the `option` dictionary argument and do not compute internally the confusion matrix as in the other cases. - `noise_distribution`: defaulted to `WhiteNoise.UNIFORM`, for non-uniform noise distributions -For `NoiseProtocol.READOUT.CORRELATEDREADOUT`, the `option` dictionary argument accepts the following options: +For `NoiseProtocol.READOUT.CORRELATED`, the `option` dictionary argument accepts the following options: - `confusion_matrix`: The square matrix representing $T(x|x')$ for each possible bitstring of length `n` qubits. Should be of size (2**n, 2**n). - `seed`: defaulted to `None`, for reproducibility purposes @@ -120,7 +120,7 @@ from qadence.measurements import Measurements # Define a noise model with options. options = {"error_probability": 0.01} -noise = NoiseHandler(protocol=NoiseProtocol.READOUT.INDEPENDENTREADOUT, options=options) +noise = NoiseHandler(protocol=NoiseProtocol.READOUT.INDEPENDENT, options=options) # Define a tomographical measurement protocol with options. options = {"n_shots": 10000} diff --git a/qadence/backends/pyqtorch/convert_ops.py b/qadence/backends/pyqtorch/convert_ops.py index e6fbfe70..83743e2a 100644 --- a/qadence/backends/pyqtorch/convert_ops.py +++ b/qadence/backends/pyqtorch/convert_ops.py @@ -374,7 +374,7 @@ def convert_readout_noise(n_qubits: int, noise: NoiseHandler) -> pyq.noise.Reado if readout_part is None: return None - if readout_part.protocol[0] == NoiseProtocol.READOUT.INDEPENDENTREADOUT: + if readout_part.protocol[0] == NoiseProtocol.READOUT.INDEPENDENT: return pyq.noise.ReadoutNoise(n_qubits, **readout_part.options[0]) else: return pyq.noise.CorrelatedReadoutNoise(**readout_part.options[0]) diff --git a/qadence/noise/protocols.py b/qadence/noise/protocols.py index 6b3f3cdc..ea3d6d46 100644 --- a/qadence/noise/protocols.py +++ b/qadence/noise/protocols.py @@ -197,10 +197,10 @@ def dephasing(self, *args: Any, **kwargs: Any) -> NoiseHandler: self.append(NoiseHandler(NoiseProtocol.ANALOG.DEPHASING, *args, **kwargs)) return self - def independent_readout(self, *args: Any, **kwargs: Any) -> NoiseHandler: - self.append(NoiseHandler(NoiseProtocol.READOUT.INDEPENDENTREADOUT, *args, **kwargs)) + def readout_independent(self, *args: Any, **kwargs: Any) -> NoiseHandler: + self.append(NoiseHandler(NoiseProtocol.READOUT.INDEPENDENT, *args, **kwargs)) return self - def correlated_readout(self, *args: Any, **kwargs: Any) -> NoiseHandler: - self.append(NoiseHandler(NoiseProtocol.READOUT.CORRELATEDREADOUT, *args, **kwargs)) + def readout_correlated(self, *args: Any, **kwargs: Any) -> NoiseHandler: + self.append(NoiseHandler(NoiseProtocol.READOUT.CORRELATED, *args, **kwargs)) return self diff --git a/qadence/types.py b/qadence/types.py index 6e06c147..80d08d2d 100644 --- a/qadence/types.py +++ b/qadence/types.py @@ -473,9 +473,9 @@ class AnalogNoise(StrEnum): class ReadoutNoise(StrEnum): """Type of readout protocol.""" - INDEPENDENTREADOUT = "IndependentReadout" + INDEPENDENT = "Independent Readout" """Simple readout protocols where each qubit is corrupted independently.""" - CORRELATEDREADOUT = "CorrelatedReadout" + CORRELATED = "Correlated Readout" """Using a confusion matrix (2**n, 2**n) for corrupting bitstrings values.""" diff --git a/tests/qadence/test_noise/test_readout.py b/tests/qadence/test_noise/test_readout.py index 112ccacc..ea8f0250 100644 --- a/tests/qadence/test_noise/test_readout.py +++ b/tests/qadence/test_noise/test_readout.py @@ -79,7 +79,7 @@ def test_readout_error_quantum_model( noiseless_samples: list[Counter] = model.sample(n_shots=n_shots) noise_protocol: NoiseHandler = NoiseHandler( - protocol=NoiseProtocol.READOUT.INDEPENDENTREADOUT, + protocol=NoiseProtocol.READOUT.INDEPENDENT, options={"error_probability": error_probability}, ) noisy_samples: list[Counter] = model.sample(noise=noise_protocol, n_shots=n_shots) @@ -96,7 +96,7 @@ def test_readout_error_quantum_model( rand_confusion = torch.rand(2**block.n_qubits, 2**block.n_qubits) rand_confusion = rand_confusion / rand_confusion.sum(dim=1, keepdim=True) corr_noise_protocol: NoiseHandler = NoiseHandler( - protocol=NoiseProtocol.READOUT.CORRELATEDREADOUT, + protocol=NoiseProtocol.READOUT.CORRELATED, options={"confusion_matrix": rand_confusion}, ) # assert difference with noiseless samples @@ -122,7 +122,7 @@ def test_readout_error_backends(backend: BackendName) -> None: samples = qd.sample(feature_map, n_shots=1000, values=inputs, backend=backend, noise=None) # introduce noise options = {"error_probability": error_probability} - noise = NoiseHandler(protocol=NoiseProtocol.READOUT.INDEPENDENTREADOUT, options=options) + noise = NoiseHandler(protocol=NoiseProtocol.READOUT.INDEPENDENT, options=options) noisy_samples = qd.sample( feature_map, n_shots=1000, values=inputs, backend=backend, noise=noise ) @@ -154,7 +154,7 @@ def test_readout_error_with_measurements( observable = hamiltonian_factory(circuit.n_qubits, detuning=Z) model = QuantumModel(circuit=circuit, observable=observable, diff_mode=DiffMode.GPSR) - noise = NoiseHandler(protocol=NoiseProtocol.READOUT.INDEPENDENTREADOUT) + noise = NoiseHandler(protocol=NoiseProtocol.READOUT.INDEPENDENT) measurement = Measurements(protocol=str(measurement_proto), options=options) noisy = model.expectation(values=inputs, measurement=measurement, noise=noise) @@ -171,14 +171,14 @@ def test_readout_error_with_measurements( def test_serialization() -> None: - noise = NoiseHandler(protocol=NoiseProtocol.READOUT.INDEPENDENTREADOUT) + noise = NoiseHandler(protocol=NoiseProtocol.READOUT.INDEPENDENT) serialized_noise = NoiseHandler._from_dict(noise._to_dict()) assert noise == serialized_noise rand_confusion = torch.rand(4, 4) rand_confusion = rand_confusion / rand_confusion.sum(dim=1, keepdim=True) noise = NoiseHandler( - protocol=NoiseProtocol.READOUT.CORRELATEDREADOUT, + protocol=NoiseProtocol.READOUT.CORRELATED, options={"seed": 0, "confusion_matrix": rand_confusion}, ) serialized_noise = NoiseHandler._from_dict(noise._to_dict()) @@ -196,8 +196,8 @@ def test_serialization() -> None: @pytest.mark.parametrize( "initial_noise", [ - NoiseHandler(protocol=NoiseProtocol.READOUT.INDEPENDENTREADOUT), - NoiseHandler(protocol=NoiseProtocol.READOUT.CORRELATEDREADOUT, options=torch.rand((4, 4))), + NoiseHandler(protocol=NoiseProtocol.READOUT.INDEPENDENT), + NoiseHandler(protocol=NoiseProtocol.READOUT.CORRELATED, options=torch.rand((4, 4))), ], ) def test_append( @@ -207,7 +207,7 @@ def test_append( with pytest.raises(ValueError): initial_noise.append(NoiseHandler(noise_config, options)) with pytest.raises(ValueError): - initial_noise.independent_readout(options) + initial_noise.readout_independent(options) with pytest.raises(ValueError): - initial_noise.correlated_readout({"confusion_matrix": torch.rand(4, 4)}) + initial_noise.readout_correlated({"confusion_matrix": torch.rand(4, 4)})