From 43a03514ae3fced6880f9182be718158e2c921c4 Mon Sep 17 00:00:00 2001 From: Charles MOUSSA Date: Tue, 12 Nov 2024 13:00:48 +0100 Subject: [PATCH 01/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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 718586353b56d19c6d7ce864092cf4ce87f61935 Mon Sep 17 00:00:00 2001 From: chMoussa Date: Fri, 22 Nov 2024 19:05:49 +0100 Subject: [PATCH 15/16] Update qadence/transpile/noise.py Co-authored-by: RolandMacDoland <9250798+RolandMacDoland@users.noreply.github.com> --- qadence/transpile/noise.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qadence/transpile/noise.py b/qadence/transpile/noise.py index 1f5127df..c8a5ea9d 100644 --- a/qadence/transpile/noise.py +++ b/qadence/transpile/noise.py @@ -40,8 +40,9 @@ 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. """ + input_block: AbstractBlock if isinstance(circuit, ConvertedCircuit): - input_block: AbstractBlock = circuit.abstract.block + input_block = circuit.abstract.block elif isinstance(circuit, QuantumCircuit): input_block = circuit.block else: From a0dcabdea916928dd7b219473ed30c786a12a008 Mon Sep 17 00:00:00 2001 From: Charles MOUSSA Date: Fri, 22 Nov 2024 23:13:17 +0100 Subject: [PATCH 16/16] rm str typing redundant --- 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 acca97cb..3c68b358 100644 --- a/qadence/noise/protocols.py +++ b/qadence/noise/protocols.py @@ -148,7 +148,7 @@ 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: + def filter(self, protocol: NoiseEnum) -> NoiseHandler | None: protocol_matches: list = list() if protocol == NoiseProtocol.READOUT: protocol_matches = [p == protocol for p in self.protocol]