diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 22fe74cea2..ba0bc512be 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -7,6 +7,9 @@ ### Breaking changes +* Removed the `QuimbMPS` class and the corresponding interface/backend from `lightning.tensor`. + [(#737)](https://github.com/PennyLaneAI/pennylane-lightning/pull/737) + * Changed the name of `default.tensor` to `lightning.tensor` with the `quimb` backend. [(#730)](https://github.com/PennyLaneAI/pennylane-lightning/pull/730) diff --git a/.github/workflows/tests_lkcpu_python.yml b/.github/workflows/tests_lkcpu_python.yml index deb557a040..576e264d6c 100644 --- a/.github/workflows/tests_lkcpu_python.yml +++ b/.github/workflows/tests_lkcpu_python.yml @@ -246,7 +246,7 @@ jobs: python -m pip install pytest-xdist cd main/ DEVICENAME=`echo ${{ matrix.pl_backend }} | sed "s/_/./g"` - PL_DEVICE=${DEVICENAME} python -m pytest tests/ $COVERAGE_FLAGS --splits 7 --group ${{ matrix.group }} \ + PL_DEVICE=${DEVICENAME} python -m pytest tests/ --exitfirst $COVERAGE_FLAGS --splits 7 --group ${{ matrix.group }} \ --store-durations --durations-path='.github/workflows/python_lightning_kokkos_test_durations.json' --splitting-algorithm=least_duration mv .github/workflows/python_lightning_kokkos_test_durations.json ${{ github.workspace }}/.test_durations-${{ matrix.exec_model }}-${{ matrix.group }} pl-device-test --device ${DEVICENAME} --skip-ops --shots=20000 $COVERAGE_FLAGS --cov-append diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 7671600d2c..eb9ad44099 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.37.0-dev17" +__version__ = "0.37.0-dev18" diff --git a/pennylane_lightning/lightning_tensor/backends/quimb/_mps.py b/pennylane_lightning/lightning_tensor/backends/quimb/_mps.py deleted file mode 100644 index e7677b4fe7..0000000000 --- a/pennylane_lightning/lightning_tensor/backends/quimb/_mps.py +++ /dev/null @@ -1,370 +0,0 @@ -# Copyright 2018-2024 Xanadu Quantum Technologies Inc. - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Class implementation for the Quimb MPS interface for simulating quantum circuits while keeping the state always in MPS form. -""" -import copy -from typing import Callable, Sequence, Union - -import pennylane as qml -import quimb.tensor as qtn -from pennylane import numpy as np -from pennylane.devices import DefaultExecutionConfig, ExecutionConfig -from pennylane.devices.preprocess import ( - decompose, - validate_device_wires, - validate_measurements, - validate_observables, -) -from pennylane.measurements import ExpectationMP, MeasurementProcess, StateMeasurement, VarianceMP -from pennylane.tape import QuantumScript, QuantumTape -from pennylane.transforms.core import TransformProgram -from pennylane.typing import Result, ResultBatch, TensorLike -from pennylane.wires import Wires - -Result_or_ResultBatch = Union[Result, ResultBatch] -QuantumTapeBatch = Sequence[QuantumTape] -QuantumTape_or_Batch = Union[QuantumTape, QuantumTapeBatch] -PostprocessingFn = Callable[[ResultBatch], Result_or_ResultBatch] - -_operations = frozenset( - { - "Identity", - "QubitUnitary", - "ControlledQubitUnitary", - "MultiControlledX", - "DiagonalQubitUnitary", - "PauliX", - "PauliY", - "PauliZ", - "MultiRZ", - "GlobalPhase", - "Hadamard", - "S", - "T", - "SX", - "CNOT", - "SWAP", - "ISWAP", - "PSWAP", - "SISWAP", - "SQISW", - "CSWAP", - "Toffoli", - "CY", - "CZ", - "PhaseShift", - "ControlledPhaseShift", - "CPhase", - "RX", - "RY", - "RZ", - "Rot", - "CRX", - "CRY", - "CRZ", - "CRot", - "IsingXX", - "IsingYY", - "IsingZZ", - "IsingXY", - "SingleExcitation", - "SingleExcitationPlus", - "SingleExcitationMinus", - "DoubleExcitation", - "QubitCarry", - "QubitSum", - "OrbitalRotation", - "QFT", - "ECR", - "BlockEncode", - } -) -# The set of supported operations. - - -_observables = frozenset( - { - "PauliX", - "PauliY", - "PauliZ", - "Hadamard", - "Hermitian", - "Identity", - "Projector", - "SparseHamiltonian", - "Hamiltonian", - "LinearCombination", - "Sum", - "SProd", - "Prod", - "Exp", - } -) -# The set of supported observables. - - -def stopping_condition(op: qml.operation.Operator) -> bool: - """A function that determines if an operation is supported by ``lightning.tensor`` for this interface.""" - return op.name in _operations # pragma: no cover - - -def accepted_observables(obs: qml.operation.Operator) -> bool: - """A function that determines if an observable is supported by ``lightning.tensor`` for this interface.""" - return obs.name in _observables # pragma: no cover - - -class QuimbMPS: - """Quimb MPS class. - - Used internally by the `LightningTensor` device. - Interfaces with `quimb` for MPS manipulation, and provides methods to execute quantum circuits. - - Args: - num_wires (int): the number of wires in the circuit. - interf_opts (dict): dictionary containing the interface options. - dtype (np.dtype): the complex type used for the MPS. - """ - - def __init__(self, num_wires, interf_opts, dtype=np.complex128): - if dtype not in [np.complex64, np.complex128]: # pragma: no cover - raise TypeError(f"Unsupported complex type: {dtype}") - - self._wires = Wires(range(num_wires)) - self._dtype = dtype - - self._init_state_opts = { - "binary": "0" * max(1, len(self._wires)), - "dtype": self._dtype.__name__, - "tags": [str(l) for l in self._wires.labels], - } - - self._gate_opts = { - "parametrize": None, - "contract": interf_opts["contract"], - "cutoff": interf_opts["cutoff"], - "max_bond": interf_opts["max_bond_dim"], - } - - self._expval_opts = { - "dtype": self._dtype.__name__, - "simplify_sequence": "ADCRS", - "simplify_atol": 0.0, - } - - self._circuitMPS = qtn.CircuitMPS(psi0=self._initial_mps()) - - @property - def interface_name(self) -> str: - """The name of this interface.""" - return "QuimbMPS interface" - - @property - def state(self) -> qtn.MatrixProductState: - """Return the current MPS handled by the interface.""" - return self._circuitMPS.psi - - def state_to_array(self) -> np.ndarray: - """Contract the MPS into a dense array.""" - return self._circuitMPS.to_dense() - - def _reset_state(self) -> None: - """Reset the MPS.""" - self._circuitMPS = qtn.CircuitMPS(psi0=self._initial_mps()) - - def _initial_mps(self) -> qtn.MatrixProductState: - r""" - Return an initial state to :math:`\ket{0}`. - - Internally, it uses `quimb`'s `MPS_computational_state` method. - - Returns: - MatrixProductState: The initial MPS of a circuit. - """ - return qtn.MPS_computational_state(**self._init_state_opts) - - def preprocess(self) -> TransformProgram: - """This function defines the device transform program to be applied for this interface. - - Returns: - TransformProgram: A transform program that when called returns :class:`~.QuantumTape`'s that the - device can natively execute as well as a postprocessing function to be called after execution. - - This interface: - - * Supports any one or two-qubit operations that provide a matrix. - * Supports any three or four-qubit operations that provide a decomposition method. - * Currently does not support finite shots. - """ - - program = TransformProgram() - - program.add_transform(validate_measurements, name=self.interface_name) - program.add_transform(validate_observables, accepted_observables, name=self.interface_name) - program.add_transform(validate_device_wires, self._wires, name=self.interface_name) - program.add_transform( - decompose, - stopping_condition=stopping_condition, - skip_initial_state_prep=True, - name=self.interface_name, - ) - program.add_transform(qml.transforms.broadcast_expand) - - return program - - # pylint: disable=unused-argument - def execute( - self, - circuits: QuantumTape_or_Batch, - execution_config: ExecutionConfig = DefaultExecutionConfig, - ) -> Result_or_ResultBatch: - """Execute a circuit or a batch of circuits and turn it into results. - - Args: - circuits (Union[QuantumTape, Sequence[QuantumTape]]): the quantum circuits to be executed - execution_config (ExecutionConfig): a datastructure with additional information required for execution - - Returns: - TensorLike, tuple[TensorLike], tuple[tuple[TensorLike]]: A numeric result of the computation. - """ - - results = [] - for circuit in circuits: - circuit = circuit.map_to_standard_wires() - results.append(self.simulate(circuit)) - - return tuple(results) - - def simulate(self, circuit: QuantumScript) -> Result: - """Simulate a single quantum script. This function assumes that all operations provide matrices. - - Args: - circuit (QuantumScript): The single circuit to simulate. - - Returns: - Tuple[TensorLike]: The results of the simulation. - """ - - self._reset_state() - - for op in circuit.operations: - self._apply_operation(op) - - if not circuit.shots: - if len(circuit.measurements) == 1: - return self.measurement(circuit.measurements[0]) - return tuple(self.measurement(mp) for mp in circuit.measurements) - - raise NotImplementedError # pragma: no cover - - def _apply_operation(self, op: qml.operation.Operator) -> None: - """Apply a single operator to the circuit, keeping the state always in a MPS form. - - Internally it uses `quimb`'s `apply_gate` method. - - Args: - op (Operator): The operation to apply. - """ - - self._circuitMPS.apply_gate(op.matrix().astype(self._dtype), *op.wires, **self._gate_opts) - - def measurement(self, measurementprocess: MeasurementProcess) -> TensorLike: - """Measure the measurement required by the circuit over the MPS. - - Args: - measurementprocess (MeasurementProcess): measurement to apply to the state. - - Returns: - TensorLike: the result of the measurement. - """ - - return self._get_measurement_function(measurementprocess)(measurementprocess) - - def _get_measurement_function( - self, measurementprocess: MeasurementProcess - ) -> Callable[[MeasurementProcess, TensorLike], TensorLike]: - """Get the appropriate method for performing a measurement. - - Args: - measurementprocess (MeasurementProcess): measurement process to apply to the state - - Returns: - Callable: function that returns the measurement result - """ - if isinstance(measurementprocess, StateMeasurement): - if isinstance(measurementprocess, ExpectationMP): - return self.expval - - if isinstance(measurementprocess, VarianceMP): - return self.var - - raise NotImplementedError # pragma: no cover - - def expval(self, measurementprocess: MeasurementProcess) -> float: - """Expectation value of the supplied observable contained in the MeasurementProcess. - - Args: - measurementprocess (StateMeasurement): measurement to apply to the MPS. - - Returns: - Expectation value of the observable. - """ - - obs = measurementprocess.obs - - result = self._local_expectation(obs.matrix(), tuple(obs.wires)) - - return result - - def var(self, measurementprocess: MeasurementProcess) -> float: - """Variance of the supplied observable contained in the MeasurementProcess. - - Args: - measurementprocess (StateMeasurement): measurement to apply to the MPS. - - Returns: - Variance of the observable. - """ - - obs = measurementprocess.obs - - obs_mat = obs.matrix() - expect_op = self.expval(measurementprocess) - expect_squar_op = self._local_expectation(obs_mat @ obs_mat.conj().T, tuple(obs.wires)) - - return expect_squar_op - np.square(expect_op) - - def _local_expectation(self, matrix, wires) -> float: - """Compute the local expectation value of a matrix on the MPS. - - Internally, it uses `quimb`'s `local_expectation` method. - - Args: - matrix (array): the matrix to compute the expectation value of. - wires (tuple[int]): the wires the matrix acts on. - - Returns: - Local expectation value of the matrix on the MPS. - """ - - # We need to copy the MPS to avoid modifying the original state - qc = copy.deepcopy(self._circuitMPS) - - exp_val = qc.local_expectation( - matrix, - wires, - **self._expval_opts, - ) - - return float(np.real(exp_val)) diff --git a/pennylane_lightning/lightning_tensor/lightning_tensor.py b/pennylane_lightning/lightning_tensor/lightning_tensor.py index 831625e09f..9916a4317d 100644 --- a/pennylane_lightning/lightning_tensor/lightning_tensor.py +++ b/pennylane_lightning/lightning_tensor/lightning_tensor.py @@ -26,15 +26,13 @@ from pennylane.tape import QuantumTape from pennylane.typing import Result, ResultBatch -from .backends.quimb._mps import QuimbMPS - Result_or_ResultBatch = Union[Result, ResultBatch] QuantumTapeBatch = Sequence[QuantumTape] QuantumTape_or_Batch = Union[QuantumTape, QuantumTapeBatch] PostprocessingFn = Callable[[ResultBatch], Result_or_ResultBatch] -_backends = frozenset({"quimb"}) +_backends = frozenset({"cutensornet"}) # The set of supported backends. _methods = frozenset({"mps"}) @@ -61,34 +59,20 @@ class LightningTensor(Device): Args: wires (int): The number of wires to initialize the device with. Defaults to ``None`` if not specified. - backend (str): Supported backend. Currently, only ``quimb`` is supported. + backend (str): Supported backend. Currently, only ``cutensornet`` is supported. method (str): Supported method. Currently, only ``mps`` is supported. shots (int): How many times the circuit should be evaluated (or sampled) to estimate the expectation values. Currently, it can only be ``None``, so that computation of statistics like expectation values and variances is performed analytically. c_dtype: Datatypes for the tensor representation. Must be one of ``np.complex64`` or ``np.complex128``. - **kwargs: keyword arguments. The following options are currently supported: - - ``max_bond_dim`` (int): Maximum bond dimension for the MPS simulator. - It corresponds to the number of Schmidt coefficients retained at the end of the SVD algorithm when applying gates. Default is ``None``. - ``cutoff`` (float): Truncation threshold for the Schmidt coefficients in a MPS simulator. Default is ``np.finfo(c_dtype).eps``. - ``contract`` (str): The contraction method for applying gates. It can be either ``auto-mps`` or ``nonlocal``. - ``nonlocal`` turns each gate into a MPO and applies it directly to the MPS, while ``auto-mps`` swaps nonlocal qubits in 2-qubit gates to be next - to each other before applying the gate, then swaps them back. Default is ``auto-mps``. + **kwargs: keyword arguments. TODO add when cuTensorNet MPS backend is available as a prototype. """ # pylint: disable=too-many-instance-attributes # So far we just consider the options for MPS simulator - _device_options = ( - "backend", - "c_dtype", - "contract", - "cutoff", - "method", - "max_bond_dim", - ) + _device_options = ("backend", "c_dtype") _new_API = True @@ -97,7 +81,7 @@ def __init__( self, *, wires=None, - backend="quimb", + backend="cutensornet", method="mps", shots=None, c_dtype=np.complex128, @@ -119,25 +103,7 @@ def __init__( self._method = method self._c_dtype = c_dtype - # options for MPS - self._max_bond_dim = kwargs.get("max_bond_dim", None) - self._cutoff = kwargs.get("cutoff", np.finfo(self._c_dtype).eps) - self._contract = kwargs.get("contract", "auto-mps") - self._interface = None - interface_opts = self._setup_execution_config().device_options - - if self.backend == "quimb" and self.method == "mps": - self._interface = QuimbMPS( - self._num_wires, - interface_opts, - self._c_dtype, - ) - - else: - raise ValueError( - f"Unsupported backend: {self.backend} or method: {self.method}" - ) # pragma: no cover for arg in kwargs: if arg not in self._device_options: @@ -211,9 +177,10 @@ def preprocess( * Does not support vector-Jacobian products. """ - config = self._setup_execution_config(execution_config) - program = self._interface.preprocess() - return program, config + # TODO: remove comments when cuTensorNet MPS backend is available as a prototype + # config = self._setup_execution_config(execution_config) + # program = self._interface.preprocess() + # return program, config def execute( self, @@ -230,7 +197,8 @@ def execute( TensorLike, tuple[TensorLike], tuple[tuple[TensorLike]]: A numeric result of the computation. """ - return self._interface.execute(circuits, execution_config) + # TODO: remove comment when cuTensorNet MPS backend is available as a prototype + # return self._interface.execute(circuits, execution_config) # pylint: disable=unused-argument def supports_derivatives( diff --git a/requirements-dev.txt b/requirements-dev.txt index da5f1ba290..e5c6db9967 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -18,5 +18,4 @@ click==8.0.4 cmake custatevec-cu12 pylint -scipy~=1.12.0 -quimb==1.8.1 +scipy diff --git a/tests/lightning_tensor/test_expval.py b/tests/lightning_tensor/test_expval.py deleted file mode 100644 index d32062bfad..0000000000 --- a/tests/lightning_tensor/test_expval.py +++ /dev/null @@ -1,398 +0,0 @@ -# Copyright 2018-2024 Xanadu Quantum Technologies Inc. - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Tests for the expectation value calculations on the LightningTensor device. -""" - -import numpy as np -import pennylane as qml -import pytest -from conftest import PHI, THETA, VARPHI, LightningDevice -from pennylane.devices import DefaultQubit - -if not LightningDevice._new_API: - pytest.skip("Exclusive tests for new API. Skipping.", allow_module_level=True) - -if LightningDevice._CPP_BINARY_AVAILABLE: - pytest.skip("Device doesn't have C++ support yet.", allow_module_level=True) - -from pennylane_lightning.lightning_tensor import LightningTensor - - -@pytest.fixture(params=[np.complex64, np.complex128]) -def dev(request): - return LightningTensor(wires=3, c_dtype=request.param) - - -def calculate_reference(tape): - dev = DefaultQubit(max_workers=1) - program, _ = dev.preprocess() - tapes, transf_fn = program([tape]) - results = dev.execute(tapes) - return transf_fn(results) - - -def execute(dev, tape): - results = dev.execute(tape) - return results - - -@pytest.mark.parametrize("theta, phi", list(zip(THETA, PHI))) -class TestExpval: - """Test expectation value calculations""" - - def test_Identity(self, theta, phi, dev, tol): - """Tests applying identities.""" - - ops = [ - qml.Identity(0), - qml.Identity((0, 1)), - qml.RX(theta, 0), - qml.Identity((1, 2)), - qml.RX(phi, 1), - ] - measurements = [qml.expval(qml.PauliZ(0))] - tape = qml.tape.QuantumScript(ops, measurements) - - result = dev.execute(tape) - expected = np.cos(theta) - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(result, expected, tol) - - def test_identity_expectation(self, theta, phi, dev, tol): - """Tests identity expectations.""" - - tape = qml.tape.QuantumScript( - [qml.RX(theta, wires=[0]), qml.RX(phi, wires=[1]), qml.CNOT(wires=[0, 1])], - [qml.expval(qml.Identity(wires=[0])), qml.expval(qml.Identity(wires=[1]))], - ) - result = dev.execute(tape) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(1.0, result, tol) - - def test_multi_wire_identity_expectation(self, theta, phi, dev, tol): - """Tests multi-wire identity.""" - - tape = qml.tape.QuantumScript( - [qml.RX(theta, wires=[0]), qml.RX(phi, wires=[1]), qml.CNOT(wires=[0, 1])], - [qml.expval(qml.Identity(wires=[0, 1]))], - ) - result = dev.execute(tape) - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(1.0, result, tol) - - @pytest.mark.parametrize( - "wires", - [([0, 1]), (["a", 1]), (["b", "a"]), ([-1, 2.5])], - ) - def test_custom_wires(self, theta, phi, tol, wires): - """Tests custom wires.""" - dev = LightningTensor(wires=wires, c_dtype=np.complex128) - - tape = qml.tape.QuantumScript( - [ - qml.RX(theta, wires=wires[0]), - qml.RX(phi, wires=wires[1]), - qml.CNOT(wires=wires), - ], - [ - qml.expval(qml.PauliZ(wires=wires[0])), - qml.expval(qml.PauliZ(wires=wires[1])), - ], - ) - - calculated_val = execute(dev, tape) - reference_val = np.array([np.cos(theta), np.cos(theta) * np.cos(phi)]) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(calculated_val, reference_val, tol) - - @pytest.mark.parametrize( - "Obs, Op, expected_fn", - [ - ( - [qml.PauliX(wires=[0]), qml.PauliX(wires=[1])], - qml.RY, - lambda theta, phi: np.array([np.sin(theta) * np.sin(phi), np.sin(phi)]), - ), - ( - [qml.PauliY(wires=[0]), qml.PauliY(wires=[1])], - qml.RX, - lambda theta, phi: np.array([0, -np.cos(theta) * np.sin(phi)]), - ), - ( - [qml.PauliZ(wires=[0]), qml.PauliZ(wires=[1])], - qml.RX, - lambda theta, phi: np.array([np.cos(theta), np.cos(theta) * np.cos(phi)]), - ), - ( - [qml.Hadamard(wires=[0]), qml.Hadamard(wires=[1])], - qml.RY, - lambda theta, phi: np.array( - [ - np.sin(theta) * np.sin(phi) + np.cos(theta), - np.cos(theta) * np.cos(phi) + np.sin(phi), - ] - ) - / np.sqrt(2), - ), - ], - ) - def test_single_wire_observables_expectation(self, Obs, Op, expected_fn, theta, phi, tol, dev): - """Test that expectation values for single wire observables are correct""" - - tape = qml.tape.QuantumScript( - [Op(theta, wires=[0]), Op(phi, wires=[1]), qml.CNOT(wires=[0, 1])], - [qml.expval(Obs[0]), qml.expval(Obs[1])], - ) - result = execute(dev, tape) - expected = expected_fn(theta, phi) - - assert np.allclose(result, expected, tol) - - def test_hermitian_expectation(self, theta, phi, tol, dev): - """Tests an Hermitian operator.""" - - with qml.tape.QuantumTape() as tape: - qml.RX(theta, wires=0) - qml.RX(phi, wires=1) - qml.RX(theta + phi, wires=2) - - for idx in range(3): - qml.expval(qml.Hermitian([[1, 0], [0, -1]], wires=[idx])) - - calculated_val = execute(dev, tape) - reference_val = calculate_reference(tape) - - assert np.allclose(calculated_val, reference_val, tol) - - def test_hamiltonian_expectation(self, theta, phi, tol, dev): - """Tests a Hamiltonian.""" - - ham = qml.Hamiltonian( - [1.0, 0.3, 0.3, 0.4], - [ - qml.PauliX(0) @ qml.PauliX(1), - qml.PauliZ(0), - qml.PauliZ(1), - qml.PauliX(0) @ qml.PauliY(1), - ], - ) - - with qml.tape.QuantumTape() as tape: - qml.RX(theta, wires=0) - qml.RX(phi, wires=1) - qml.RX(theta + phi, wires=2) - - qml.expval(ham) - - calculated_val = execute(dev, tape) - reference_val = calculate_reference(tape) - - assert np.allclose(calculated_val, reference_val, tol) - - def test_sparse_hamiltonian_expectation(self, theta, phi, tol, dev): - """Tests a Hamiltonian.""" - - ham = qml.SparseHamiltonian( - qml.Hamiltonian( - [1.0, 0.3, 0.3, 0.4], - [ - qml.PauliX(0) @ qml.PauliX(1), - qml.PauliZ(0), - qml.PauliZ(1), - qml.PauliX(0) @ qml.PauliY(1), - ], - ).sparse_matrix(), - wires=[0, 1], - ) - - with qml.tape.QuantumTape() as tape: - qml.RX(theta, wires=0) - qml.RX(phi, wires=1) - - qml.expval(ham) - - calculated_val = execute(dev, tape) - reference_val = calculate_reference(tape) - - assert np.allclose(calculated_val, reference_val, tol) - - -@pytest.mark.parametrize("phi", PHI) -class TestOperatorArithmetic: - """Test integration with SProd, Prod, and Sum.""" - - @pytest.mark.parametrize( - "obs", - [ - qml.s_prod(0.5, qml.PauliZ(0)), - qml.prod(qml.PauliZ(0), qml.PauliX(1)), - qml.sum(qml.PauliZ(0), qml.PauliX(1)), - ], - ) - def test_op_math(self, phi, dev, obs, tol): - """Tests the `SProd`, `Prod`, and `Sum` classes.""" - - tape = qml.tape.QuantumScript( - [ - qml.RX(phi, wires=[0]), - qml.Hadamard(wires=[1]), - qml.PauliZ(wires=[1]), - qml.RX(-1.1 * phi, wires=[1]), - ], - [qml.expval(obs)], - ) - - calculated_val = execute(dev, tape) - reference_val = calculate_reference(tape) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(calculated_val, reference_val, tol) - - def test_integration(self, phi, dev, tol): - """Test a Combination of `Sum`, `SProd`, and `Prod`.""" - - obs = qml.sum( - qml.s_prod(2.3, qml.PauliZ(0)), - -0.5 * qml.prod(qml.PauliY(0), qml.PauliZ(1)), - ) - - tape = qml.tape.QuantumScript( - [qml.RX(phi, wires=[0]), qml.RX(-1.1 * phi, wires=[0])], - [qml.expval(obs)], - ) - - calculated_val = execute(dev, tape) - reference_val = calculate_reference(tape) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(calculated_val, reference_val, tol) - - -@pytest.mark.parametrize("theta, phi, varphi", list(zip(THETA, PHI, VARPHI))) -class TestTensorExpval: - """Test tensor expectation values""" - - def test_PauliX_PauliY(self, theta, phi, varphi, dev, tol): - """Tests a tensor product involving PauliX and PauliY.""" - - with qml.tape.QuantumTape() as tape: - qml.RX(theta, wires=[0]) - qml.RX(phi, wires=[1]) - qml.RX(varphi, wires=[2]) - qml.CNOT(wires=[0, 1]) - qml.CNOT(wires=[1, 2]) - qml.expval(qml.PauliX(0) @ qml.PauliY(2)) - - calculated_val = execute(dev, tape) - reference_val = calculate_reference(tape) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(calculated_val, reference_val, tol) - - def test_PauliZ_identity(self, theta, phi, varphi, dev, tol): - """Tests a tensor product involving PauliZ and Identity.""" - - with qml.tape.QuantumTape() as tape: - qml.Identity(wires=[0]) - qml.RX(theta, wires=[0]) - qml.RX(phi, wires=[1]) - qml.RX(varphi, wires=[2]) - qml.CNOT(wires=[0, 1]) - qml.CNOT(wires=[1, 2]) - qml.expval(qml.PauliZ(0) @ qml.Identity(1) @ qml.PauliZ(2)) - - calculated_val = execute(dev, tape) - reference_val = calculate_reference(tape) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(calculated_val, reference_val, tol) - - def test_PauliZ_hadamard_PauliY(self, theta, phi, varphi, dev, tol): - """Tests a tensor product involving PauliY, PauliZ and Hadamard.""" - - with qml.tape.QuantumTape() as tape: - qml.RX(theta, wires=[0]) - qml.RX(phi, wires=[1]) - qml.RX(varphi, wires=[2]) - qml.CNOT(wires=[0, 1]) - qml.CNOT(wires=[1, 2]) - qml.expval(qml.PauliZ(0) @ qml.Hadamard(1) @ qml.PauliY(2)) - - calculated_val = execute(dev, tape) - reference_val = calculate_reference(tape) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(calculated_val, reference_val, tol) - - -# Define the parameter values -THETA = np.linspace(0.11, 1, 3) -PHI = np.linspace(0.32, 1, 3) - - -@pytest.mark.parametrize("theta, phi", list(zip(THETA, PHI))) -def test_multi_qubit_gates(theta, phi, dev, tol): - """Tests a simple circuit with multi-qubit gates.""" - - ops = [ - qml.PauliX(wires=[0]), - qml.RX(theta, wires=[0]), - qml.CSWAP(wires=[7, 0, 5]), - qml.RX(phi, wires=[1]), - qml.CNOT(wires=[3, 4]), - qml.DoubleExcitation(phi, wires=[1, 2, 3, 4]), - qml.CZ(wires=[4, 5]), - qml.Hadamard(wires=[4]), - qml.CCZ(wires=[0, 1, 2]), - qml.CSWAP(wires=[2, 3, 4]), - qml.QFT(wires=[0, 1, 2]), - qml.CNOT(wires=[2, 4]), - qml.Toffoli(wires=[0, 1, 2]), - qml.DoubleExcitation(phi, wires=[0, 1, 3, 4]), - ] - - meas = [ - qml.expval(qml.PauliY(wires=[2])), - qml.expval(qml.Hamiltonian([1, 5, 6], [qml.Z(6), qml.X(0), qml.Hadamard(4)])), - qml.expval( - qml.Hamiltonian( - [4, 5, 7], - [ - qml.Z(6) @ qml.Y(4), - qml.X(7), - qml.Hadamard(4), - ], - ) - ), - ] - - tape = qml.tape.QuantumScript(ops=ops, measurements=meas) - - reference_val = calculate_reference(tape) - dev = LightningTensor(wires=tape.wires, c_dtype=np.complex128) - calculated_val = dev.execute(tape) - - assert np.allclose(calculated_val, reference_val) diff --git a/tests/lightning_tensor/test_lightning_tensor.py b/tests/lightning_tensor/test_lightning_tensor.py index aacc94bad3..4bb2c2d7fd 100644 --- a/tests/lightning_tensor/test_lightning_tensor.py +++ b/tests/lightning_tensor/test_lightning_tensor.py @@ -68,16 +68,13 @@ def test_invalid_method(method): def test_invalid_keyword_arg(): """Test an invalid keyword argument.""" - with pytest.raises( - TypeError, - match=f"Unexpected argument: fake_arg during initialization of the lightning.tensor device.", - ): + with pytest.raises(TypeError): LightningTensor(fake_arg=None) def test_invalid_shots(): """Test that an error is raised if finite number of shots are requestd.""" - with pytest.raises(ValueError, match="lightning.tensor does not support finite shots."): + with pytest.raises(ValueError): LightningTensor(shots=5) diff --git a/tests/lightning_tensor/test_quimb_mps.py b/tests/lightning_tensor/test_quimb_mps.py deleted file mode 100644 index 549ab28243..0000000000 --- a/tests/lightning_tensor/test_quimb_mps.py +++ /dev/null @@ -1,235 +0,0 @@ -# Copyright 2018-2024 Xanadu Quantum Technologies Inc. - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Unit tests for the ``quimb`` interface. -""" - -import itertools -import math - -import numpy as np -import pennylane as qml -import pytest -import quimb.tensor as qtn -from conftest import LightningDevice # tested device -from pennylane.devices import DefaultQubit -from pennylane.wires import Wires -from scipy.sparse import csr_matrix - -from pennylane_lightning.lightning_tensor import LightningTensor - -if not LightningDevice._new_API: - pytest.skip("Exclusive tests for new API. Skipping.", allow_module_level=True) - -if LightningDevice._CPP_BINARY_AVAILABLE: - pytest.skip("Device doesn't have C++ support yet.", allow_module_level=True) - -THETA = np.linspace(0.11, 1, 3) -PHI = np.linspace(0.32, 1, 3) - -# gates for which interface support is tested -ops = { - "Identity": qml.Identity(wires=[0]), - "BlockEncode": qml.BlockEncode([[0.1, 0.2], [0.3, 0.4]], wires=[0, 1]), - "CNOT": qml.CNOT(wires=[0, 1]), - "CRX": qml.CRX(0, wires=[0, 1]), - "CRY": qml.CRY(0, wires=[0, 1]), - "CRZ": qml.CRZ(0, wires=[0, 1]), - "CRot": qml.CRot(0, 0, 0, wires=[0, 1]), - "CSWAP": qml.CSWAP(wires=[0, 1, 2]), - "CZ": qml.CZ(wires=[0, 1]), - "CCZ": qml.CCZ(wires=[0, 1, 2]), - "CY": qml.CY(wires=[0, 1]), - "CH": qml.CH(wires=[0, 1]), - "DiagonalQubitUnitary": qml.DiagonalQubitUnitary(np.array([1, 1]), wires=[0]), - "Hadamard": qml.Hadamard(wires=[0]), - "MultiRZ": qml.MultiRZ(0, wires=[0]), - "PauliX": qml.X(0), - "PauliY": qml.Y(0), - "PauliZ": qml.Z(0), - "X": qml.X([0]), - "Y": qml.Y([0]), - "Z": qml.Z([0]), - "PhaseShift": qml.PhaseShift(0, wires=[0]), - "PCPhase": qml.PCPhase(0, 1, wires=[0, 1]), - "ControlledPhaseShift": qml.ControlledPhaseShift(0, wires=[0, 1]), - "CPhaseShift00": qml.CPhaseShift00(0, wires=[0, 1]), - "CPhaseShift01": qml.CPhaseShift01(0, wires=[0, 1]), - "CPhaseShift10": qml.CPhaseShift10(0, wires=[0, 1]), - "QubitUnitary": qml.QubitUnitary(np.eye(2), wires=[0]), - "SpecialUnitary": qml.SpecialUnitary(np.array([0.2, -0.1, 2.3]), wires=1), - "ControlledQubitUnitary": qml.ControlledQubitUnitary(np.eye(2), control_wires=[1], wires=[0]), - "MultiControlledX": qml.MultiControlledX(wires=[1, 2, 0]), - "IntegerComparator": qml.IntegerComparator(1, geq=True, wires=[0, 1, 2]), - "RX": qml.RX(0, wires=[0]), - "RY": qml.RY(0, wires=[0]), - "RZ": qml.RZ(0, wires=[0]), - "Rot": qml.Rot(0, 0, 0, wires=[0]), - "S": qml.S(wires=[0]), - "Adjoint(S)": qml.adjoint(qml.S(wires=[0])), - "SWAP": qml.SWAP(wires=[0, 1]), - "ISWAP": qml.ISWAP(wires=[0, 1]), - "PSWAP": qml.PSWAP(0, wires=[0, 1]), - "ECR": qml.ECR(wires=[0, 1]), - "Adjoint(ISWAP)": qml.adjoint(qml.ISWAP(wires=[0, 1])), - "T": qml.T(wires=[0]), - "Adjoint(T)": qml.adjoint(qml.T(wires=[0])), - "SX": qml.SX(wires=[0]), - "Adjoint(SX)": qml.adjoint(qml.SX(wires=[0])), - "Toffoli": qml.Toffoli(wires=[0, 1, 2]), - "QFT": qml.templates.QFT(wires=[0, 1, 2]), - "IsingXX": qml.IsingXX(0, wires=[0, 1]), - "IsingYY": qml.IsingYY(0, wires=[0, 1]), - "IsingZZ": qml.IsingZZ(0, wires=[0, 1]), - "IsingXY": qml.IsingXY(0, wires=[0, 1]), - "SingleExcitation": qml.SingleExcitation(0, wires=[0, 1]), - "SingleExcitationPlus": qml.SingleExcitationPlus(0, wires=[0, 1]), - "SingleExcitationMinus": qml.SingleExcitationMinus(0, wires=[0, 1]), - "DoubleExcitation": qml.DoubleExcitation(0, wires=[0, 1, 2, 3]), - "QubitCarry": qml.QubitCarry(wires=[0, 1, 2, 3]), - "QubitSum": qml.QubitSum(wires=[0, 1, 2]), - "PauliRot": qml.PauliRot(0, "XXYY", wires=[0, 1, 2, 3]), - "U1": qml.U1(0, wires=0), - "U2": qml.U2(0, 0, wires=0), - "U3": qml.U3(0, 0, 0, wires=0), - "SISWAP": qml.SISWAP(wires=[0, 1]), - "Adjoint(SISWAP)": qml.adjoint(qml.SISWAP(wires=[0, 1])), - "OrbitalRotation": qml.OrbitalRotation(0, wires=[0, 1, 2, 3]), - "FermionicSWAP": qml.FermionicSWAP(0, wires=[0, 1]), - "GlobalPhase": qml.GlobalPhase(0.123, wires=[0, 1]), -} - -all_ops = ops.keys() - -# observables for which interface support is tested -obs = { - "Identity": qml.Identity(wires=[0]), - "Hadamard": qml.Hadamard(wires=[0]), - "Hermitian": qml.Hermitian(np.eye(2), wires=[0]), - "PauliX": qml.PauliX(0), - "PauliY": qml.PauliY(0), - "PauliZ": qml.PauliZ(0), - "X": qml.X(0), - "Y": qml.Y(0), - "Z": qml.Z(0), - "Projector": [ - qml.Projector(np.array([1]), wires=[0]), - qml.Projector(np.array([0, 1]), wires=[0]), - ], - "SparseHamiltonian": qml.SparseHamiltonian(csr_matrix(np.eye(8)), wires=[0, 1, 2]), - "Hamiltonian": qml.Hamiltonian([1, 1], [qml.Z(0), qml.X(0)]), - "LinearCombination": qml.ops.LinearCombination([1, 1], [qml.Z(0), qml.X(0)]), -} - -all_obs = obs.keys() - - -@pytest.mark.parametrize("backend", ["quimb"]) -@pytest.mark.parametrize("method", ["mps"]) -class TestQuimbMPS: - """Tests for the MPS method.""" - - @pytest.mark.parametrize("num_wires", [None, 4]) - @pytest.mark.parametrize("c_dtype", [np.complex64, np.complex128]) - @pytest.mark.parametrize("max_bond_dim", [None, 10]) - @pytest.mark.parametrize("cutoff", [1e-16, 1e-12]) - @pytest.mark.parametrize("contract", ["auto-mps", "nonlocal"]) - def test_device_init(self, num_wires, c_dtype, backend, method, max_bond_dim, cutoff, contract): - """Test the class initialization with different arguments and returned properties.""" - - kwargs = {"max_bond_dim": max_bond_dim, "cutoff": cutoff, "contract": contract} - - wires = Wires(range(num_wires)) if num_wires else None - dev = LightningTensor( - wires=wires, backend=backend, method=method, c_dtype=c_dtype, **kwargs - ) - assert isinstance(dev._interface.state, qtn.MatrixProductState) - assert isinstance(dev._interface.state_to_array(), np.ndarray) - - _, config = dev.preprocess() - assert config.device_options["c_dtype"] == c_dtype - assert config.device_options["backend"] == backend - assert config.device_options["method"] == method - assert config.device_options["max_bond_dim"] == max_bond_dim - assert config.device_options["cutoff"] == cutoff - assert config.device_options["contract"] == contract - - @pytest.mark.parametrize("operation", all_ops) - def test_supported_gates_can_be_implemented(self, operation, backend, method): - """Test that the interface can implement all its supported gates.""" - - dev = LightningTensor( - wires=Wires(range(4)), backend=backend, method=method, c_dtype=np.complex64 - ) - - tape = qml.tape.QuantumScript( - [ops[operation]], - [qml.expval(qml.Identity(wires=0))], - ) - - result = dev.execute(circuits=tape) - assert np.allclose(result, 1.0) - - @pytest.mark.parametrize("observable", all_obs) - def test_supported_observables_can_be_implemented(self, observable, backend, method): - """Test that the interface can implement all its supported observables.""" - dev = LightningTensor( - wires=Wires(range(3)), backend=backend, method=method, c_dtype=np.complex64 - ) - - if observable == "Projector": - for o in obs[observable]: - tape = qml.tape.QuantumScript( - [qml.PauliX(0)], - [qml.expval(o)], - ) - result = dev.execute(circuits=tape) - assert isinstance(result, (float, np.ndarray)) - - else: - tape = qml.tape.QuantumScript( - [qml.PauliX(0)], - [qml.expval(obs[observable])], - ) - result = dev.execute(circuits=tape) - assert isinstance(result, (float, np.ndarray)) - - def test_not_implemented_meas(self, backend, method): - """Tests that support only exists for `qml.expval` and `qml.var` so far.""" - - ops = [qml.Identity(0)] - measurements = [qml.probs(qml.PauliZ(0))] - tape = qml.tape.QuantumScript(ops, measurements) - - dev = LightningTensor( - wires=tape.wires, backend=backend, method=method, c_dtype=np.complex64 - ) - - with pytest.raises(NotImplementedError): - dev.execute(tape) - - def test_not_implemented_shots(self, backend, method): - """Tests that this interface does not support measurements with finite shots.""" - - ops = [qml.Identity(0)] - measurements = [qml.expval(qml.PauliZ(0))] - tape = qml.tape.QuantumScript(ops, measurements) - tape._shots = 5 - - dev = LightningTensor( - wires=tape.wires, backend=backend, method=method, c_dtype=np.complex64 - ) - - with pytest.raises(NotImplementedError): - dev.execute(tape) diff --git a/tests/lightning_tensor/test_var.py b/tests/lightning_tensor/test_var.py deleted file mode 100644 index 2cf3a50bdf..0000000000 --- a/tests/lightning_tensor/test_var.py +++ /dev/null @@ -1,407 +0,0 @@ -# Copyright 2018-2024 Xanadu Quantum Technologies Inc. - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Tests for the variance calculation on the LightningTensor device. -""" -import numpy as np -import pennylane as qml -import pytest -from conftest import PHI, THETA, VARPHI, LightningDevice -from pennylane.tape import QuantumScript - -if not LightningDevice._new_API: - pytest.skip("Exclusive tests for new API. Skipping.", allow_module_level=True) - -if LightningDevice._CPP_BINARY_AVAILABLE: - pytest.skip("Device doesn't have C++ support yet.", allow_module_level=True) - - -from pennylane_lightning.lightning_tensor import LightningTensor - - -@pytest.fixture(params=[np.complex64, np.complex128]) -def dev(request): - return LightningTensor(wires=3, c_dtype=request.param) - - -def calculate_reference(tape): - dev = qml.device("default.qubit", max_workers=1) - program, _ = dev.preprocess() - tapes, transf_fn = program([tape]) - results = dev.execute(tapes) - return transf_fn(results) - - -def execute(dev, tape): - results = dev.execute(tape) - return results - - -@pytest.mark.parametrize("theta, phi", list(zip(THETA, PHI))) -class TestVar: - """Tests for the variance""" - - def test_Identity(self, theta, phi, dev): - """Tests applying identities.""" - - ops = [ - qml.Identity(0), - qml.Identity((0, 1)), - qml.RX(theta, 0), - qml.Identity((1, 2)), - qml.RX(phi, 1), - ] - measurements = [qml.var(qml.PauliZ(0))] - tape = qml.tape.QuantumScript(ops, measurements) - - result = dev.execute(tape) - expected = 1 - np.cos(theta) ** 2 - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(result, expected, atol=tol, rtol=0) - - def test_identity_variance(self, theta, phi, dev): - """Tests identity variances.""" - - tape = qml.tape.QuantumScript( - [qml.RX(theta, wires=[0]), qml.RX(phi, wires=[1]), qml.CNOT(wires=[0, 1])], - [qml.var(qml.Identity(wires=[0])), qml.var(qml.Identity(wires=[1]))], - ) - result = dev.execute(tape) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - assert np.allclose(0.0, result, atol=tol, rtol=0) - - def test_multi_wire_identity_variance(self, theta, phi, dev): - """Tests multi-wire identity.""" - - tape = qml.tape.QuantumScript( - [qml.RX(theta, wires=[0]), qml.RX(phi, wires=[1]), qml.CNOT(wires=[0, 1])], - [qml.var(qml.Identity(wires=[0, 1]))], - ) - result = dev.execute(tape) - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - assert np.allclose(0.0, result, atol=tol, rtol=0) - - @pytest.mark.parametrize( - "wires", - [([0, 1]), (["a", 1]), (["b", "a"]), ([-1, 2.5])], - ) - def test_custom_wires(self, theta, phi, wires): - """Tests custom wires.""" - device = LightningTensor(wires=wires, c_dtype=np.complex128) - - tape = qml.tape.QuantumScript( - [ - qml.RX(theta, wires=wires[0]), - qml.RX(phi, wires=wires[1]), - qml.CNOT(wires=wires), - ], - [qml.var(qml.PauliZ(wires=wires[0])), qml.var(qml.PauliZ(wires=wires[1]))], - ) - - calculated_val = execute(device, tape) - reference_val = np.array( - [1 - np.cos(theta) ** 2, 1 - np.cos(theta) ** 2 * np.cos(phi) ** 2] - ) - - tol = 1e-5 if device.c_dtype == np.complex64 else 1e-7 - assert np.allclose(calculated_val, reference_val, atol=tol, rtol=0) - - @pytest.mark.parametrize( - "Obs, Op, expected_fn", - [ - ( - [qml.PauliX(wires=[0]), qml.PauliX(wires=[1])], - qml.RY, - lambda theta, phi: np.array( - [1 - np.sin(theta) ** 2 * np.sin(phi) ** 2, 1 - np.sin(phi) ** 2] - ), - ), - ( - [qml.PauliY(wires=[0]), qml.PauliY(wires=[1])], - qml.RX, - lambda theta, phi: np.array([1, 1 - np.cos(theta) ** 2 * np.sin(phi) ** 2]), - ), - ( - [qml.PauliZ(wires=[0]), qml.PauliZ(wires=[1])], - qml.RX, - lambda theta, phi: np.array( - [1 - np.cos(theta) ** 2, 1 - np.cos(theta) ** 2 * np.cos(phi) ** 2] - ), - ), - ( - [qml.Hadamard(wires=[0]), qml.Hadamard(wires=[1])], - qml.RY, - lambda theta, phi: np.array( - [ - 1 - (np.sin(theta) * np.sin(phi) + np.cos(theta)) ** 2 / 2, - 1 - (np.cos(theta) * np.cos(phi) + np.sin(phi)) ** 2 / 2, - ] - ), - ), - ], - ) - def test_single_wire_observables_variance(self, Obs, Op, expected_fn, theta, phi, dev): - """Test that variance values for single wire observables are correct""" - - tape = qml.tape.QuantumScript( - [Op(theta, wires=[0]), Op(phi, wires=[1]), qml.CNOT(wires=[0, 1])], - [qml.var(Obs[0]), qml.var(Obs[1])], - ) - result = execute(dev, tape) - expected = expected_fn(theta, phi) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - assert np.allclose(result, expected, atol=tol, rtol=0) - - def test_hermitian_variance(self, theta, phi, dev): - """Tests an Hermitian operator.""" - - with qml.tape.QuantumTape() as tape: - qml.RX(theta, wires=0) - qml.RX(phi, wires=1) - qml.RX(theta + phi, wires=2) - - for idx in range(3): - qml.var(qml.Hermitian([[1, 0], [0, -1]], wires=[idx])) - - calculated_val = execute(dev, tape) - reference_val = calculate_reference(tape) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - assert np.allclose(calculated_val, reference_val, atol=tol, rtol=0) - - def test_hamiltonian_variance(self, theta, phi, dev): - """Tests a Hamiltonian.""" - - ham = qml.Hamiltonian( - [1.0, 0.3, 0.3, 0.4], - [ - qml.PauliX(0) @ qml.PauliX(1), - qml.PauliZ(0), - qml.PauliZ(1), - qml.PauliX(0) @ qml.PauliY(1), - ], - ) - - with qml.tape.QuantumTape() as tape1: - qml.RX(theta, wires=0) - qml.RX(phi, wires=1) - qml.RX(theta + phi, wires=2) - - qml.var(ham) - - tape2 = QuantumScript(tape1.operations, [qml.var(qml.dot(ham.coeffs, ham.ops))]) - - calculated_val = execute(dev, tape1) - reference_val = calculate_reference(tape2) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - assert np.allclose(calculated_val, reference_val, atol=tol, rtol=0) - - def test_sparse_hamiltonian_variance(self, theta, phi, dev): - """Tests a Hamiltonian.""" - - ham = qml.SparseHamiltonian( - qml.Hamiltonian( - [1.0, 0.3, 0.3, 0.4], - [ - qml.PauliX(0) @ qml.PauliX(1), - qml.PauliZ(0), - qml.PauliZ(1), - qml.PauliX(0) @ qml.PauliY(1), - ], - ).sparse_matrix(), - wires=[0, 1], - ) - - with qml.tape.QuantumTape() as tape1: - qml.RX(theta, wires=0) - qml.RX(phi, wires=1) - - qml.var(ham) - - tape2 = QuantumScript( - tape1.operations, [qml.var(qml.Hermitian(ham.matrix(), wires=[0, 1]))] - ) - - calculated_val = execute(dev, tape1) - reference_val = calculate_reference(tape2) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - assert np.allclose(calculated_val, reference_val, atol=tol, rtol=0) - - -@pytest.mark.parametrize("phi", PHI) -class TestOperatorArithmetic: - """Test integration with SProd, Prod, and Sum.""" - - @pytest.mark.parametrize( - "obs", - [ - qml.s_prod(0.5, qml.PauliZ(0)), - qml.prod(qml.PauliZ(0), qml.PauliX(1)), - qml.sum(qml.PauliZ(0), qml.PauliX(1)), - ], - ) - def test_op_math(self, phi, dev, obs, tol): - """Tests the `SProd`, `Prod`, and `Sum` classes.""" - - tape = qml.tape.QuantumScript( - [ - qml.RX(phi, wires=[0]), - qml.Hadamard(wires=[1]), - qml.PauliZ(wires=[1]), - qml.RX(-1.1 * phi, wires=[1]), - ], - [qml.var(obs)], - ) - - calculated_val = execute(dev, tape) - reference_val = calculate_reference(tape) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(calculated_val, reference_val, tol) - - def test_integration(self, phi, dev, tol): - """Test a Combination of `Sum`, `SProd`, and `Prod`.""" - - obs = qml.sum( - qml.s_prod(2.3, qml.PauliZ(0)), - -0.5 * qml.prod(qml.PauliY(0), qml.PauliZ(1)), - ) - - tape = qml.tape.QuantumScript( - [qml.RX(phi, wires=[0]), qml.RX(-1.1 * phi, wires=[0])], - [qml.var(obs)], - ) - - calculated_val = execute(dev, tape) - reference_val = calculate_reference(tape) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(calculated_val, reference_val, tol) - - -@pytest.mark.parametrize("theta, phi, varphi", list(zip(THETA, PHI, VARPHI))) -class TestTensorVar: - """Test tensor variances""" - - def test_PauliX_PauliY(self, theta, phi, varphi, dev, tol): - """Tests a tensor product involving PauliX and PauliY.""" - - with qml.tape.QuantumTape() as tape: - qml.RX(theta, wires=[0]) - qml.RX(phi, wires=[1]) - qml.RX(varphi, wires=[2]) - qml.CNOT(wires=[0, 1]) - qml.CNOT(wires=[1, 2]) - qml.var(qml.PauliX(0) @ qml.PauliY(2)) - - calculated_val = execute(dev, tape) - reference_val = calculate_reference(tape) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(calculated_val, reference_val, tol) - - def test_PauliZ_identity(self, theta, phi, varphi, dev, tol): - """Tests a tensor product involving PauliZ and Identity.""" - - with qml.tape.QuantumTape() as tape: - qml.Identity(wires=[0]) - qml.RX(theta, wires=[0]) - qml.RX(phi, wires=[1]) - qml.RX(varphi, wires=[2]) - qml.CNOT(wires=[0, 1]) - qml.CNOT(wires=[1, 2]) - qml.var(qml.PauliZ(0) @ qml.Identity(1) @ qml.PauliZ(2)) - - calculated_val = execute(dev, tape) - reference_val = calculate_reference(tape) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(calculated_val, reference_val, tol) - - def test_PauliZ_hadamard_PauliY(self, theta, phi, varphi, dev, tol): - """Tests a tensor product involving PauliY, PauliZ and Hadamard.""" - - with qml.tape.QuantumTape() as tape: - qml.RX(theta, wires=[0]) - qml.RX(phi, wires=[1]) - qml.RX(varphi, wires=[2]) - qml.CNOT(wires=[0, 1]) - qml.CNOT(wires=[1, 2]) - qml.var(qml.PauliZ(0) @ qml.Hadamard(1) @ qml.PauliY(2)) - - calculated_val = execute(dev, tape) - reference_val = calculate_reference(tape) - - tol = 1e-5 if dev.c_dtype == np.complex64 else 1e-7 - - assert np.allclose(calculated_val, reference_val, tol) - - -# Define the parameter values -THETA = np.linspace(0.11, 1, 3) -PHI = np.linspace(0.32, 1, 3) - - -@pytest.mark.parametrize("theta, phi", list(zip(THETA, PHI))) -def test_multi_qubit_gates(theta, phi, dev, tol): - """Tests a simple circuit with multi-qubit gates.""" - - ops = [ - qml.PauliX(wires=[0]), - qml.RX(theta, wires=[0]), - qml.CSWAP(wires=[7, 0, 5]), - qml.RX(phi, wires=[1]), - qml.CNOT(wires=[3, 4]), - qml.DoubleExcitation(phi, wires=[1, 2, 3, 4]), - qml.CZ(wires=[4, 5]), - qml.Hadamard(wires=[4]), - qml.CCZ(wires=[0, 1, 2]), - qml.CSWAP(wires=[2, 3, 4]), - qml.QFT(wires=[0, 1, 2]), - qml.CNOT(wires=[2, 4]), - qml.Toffoli(wires=[0, 1, 2]), - qml.DoubleExcitation(phi, wires=[0, 1, 3, 4]), - ] - - meas = [ - qml.var(qml.PauliY(wires=[2])), - qml.var(qml.Hamiltonian([1, 5, 6], [qml.Z(6), qml.X(0), qml.Hadamard(4)])), - qml.var( - qml.Hamiltonian( - [4, 5, 7], - [ - qml.Z(6) @ qml.Y(4), - qml.X(0), - qml.Hadamard(7), - ], - ) - ), - ] - - tape = qml.tape.QuantumScript(ops=ops, measurements=meas) - - reference_val = calculate_reference(tape) - dev = LightningTensor(wires=tape.wires, c_dtype=np.complex128) - calculated_val = dev.execute(tape) - - assert np.allclose(calculated_val, reference_val)