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