diff --git a/cqlib_adapter/pennylane_ext/device.py b/cqlib_adapter/pennylane_ext/device.py index 66fb8ec95e58cd7982e65dcacf4900142e53e49f..a1af95011253be7ac44efb457525532cbd770d28 100644 --- a/cqlib_adapter/pennylane_ext/device.py +++ b/cqlib_adapter/pennylane_ext/device.py @@ -116,7 +116,7 @@ class CQLibDevice(Device): if measurement.obs.name == "PauliX": new_ops.append(qml.Hadamard(wires=measurement.obs.wires)) elif measurement.obs.name == "PauliY": - new_ops.append(qml.S(wires=measurement.obs.wires).inv()) + new_ops.append(qml.adjoint(qml.S)(wires=measurement.obs.wires)) new_ops.append(qml.Hadamard(wires=measurement.obs.wires)) # Convert circuit to QCIS format diff --git a/tests/test_pennylane/test_backends.py b/tests/test_pennylane/test_backends.py new file mode 100644 index 0000000000000000000000000000000000000000..531def395c1a08329f0e144d7a0c5c49b852d9d6 --- /dev/null +++ b/tests/test_pennylane/test_backends.py @@ -0,0 +1,81 @@ +# test_backends.py +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +import os +import pytest +import pennylane as qml +from pennylane import numpy as np +from pennylane.devices import Device + +# Configuration parameters +TOKEN = os.getenv("CQLIB_TOKEN", None) +SHOTS = 500 +WIRES = 2 +INITIAL_PARAMS = np.array([0.5, 0.8]) + + +def create_device( + backend_name: str, shots: int = SHOTS, wires: int = WIRES +) -> Device: + """Create a quantum device instance. + + Args: + backend_name (str): Name of the quantum backend to use. + shots (int): Number of measurement shots. + wires (int): Number of quantum wires (qubits). + + Returns: + Device: Configured quantum device instance. + """ + return qml.device( + "cqlib.device", + wires=wires, + shots=shots, + cqlib_backend_name=backend_name, + login_key=TOKEN if backend_name != "default" else None, + ) + + +@pytest.mark.parametrize("backend_name", ["tianyan504", "tianyan_sw", "default"]) +def test_backend_runs(backend_name): + """Test that circuits can run successfully on a given backend. + + Args: + backend_name (str): The backend to test. + """ + device = create_device(backend_name) + + @qml.qnode(device) + def circuit_probs(params: np.ndarray) -> np.ndarray: + """Circuit returning probability distribution.""" + qml.RX(params[0], wires=0) + qml.RY(params[1], wires=1) + qml.CNOT(wires=[0, 1]) + return qml.probs(wires=[0, 1]) + + @qml.qnode(device) + def circuit_expval(params: np.ndarray) -> float: + """Circuit returning expectation value.""" + qml.RX(params[0], wires=0) + qml.RY(params[1], wires=1) + qml.CNOT(wires=[0, 1]) + return qml.expval(qml.PauliZ(0)) + + params = INITIAL_PARAMS.copy() + probs = circuit_probs(params) + expval = circuit_expval(params) + + # Assertions + assert np.isclose(np.sum(probs), 1.0, atol=1e-6) + assert isinstance(expval, (float, np.floating, np.ndarray)) + diff --git a/tests/test_pennylane/test.py b/tests/test_pennylane/test_gradients.py similarity index 31% rename from tests/test_pennylane/test.py rename to tests/test_pennylane/test_gradients.py index 449b2c0c2ff1239c01dee04e8cd84d6a746ec608..2cf67a85cdfc7ca542c78d71c3c6a9e95371f101 100644 --- a/tests/test_pennylane/test.py +++ b/tests/test_pennylane/test_gradients.py @@ -1,3 +1,4 @@ +# test_gradients.py # This code is part of cqlib. # # Copyright (C) 2025 China Telecom Quantum Group. @@ -9,35 +10,34 @@ # Any modifications or derivative works of this code must retain this # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. - - +import os +import pytest import pennylane as qml from pennylane import numpy as np from pennylane.devices import Device -# Configuration parameters -TOKEN = "your_token" - +# Configuration parameters +TOKEN = os.getenv("CQLIB_TOKEN", None) DEFAULT_BACKEND = "default" SHOTS = 500 WIRES = 2 STEP_SIZE = 0.1 -TRAINING_STEPS = 10 +TRAINING_STEPS = 3 INITIAL_PARAMS = np.array([0.5, 0.8]) def create_device( - backend_name: str, shots: int = SHOTS, wires: int = WIRES + backend_name: str = DEFAULT_BACKEND, shots: int = SHOTS, wires: int = WIRES ) -> Device: - """Creates a quantum device instance. + """Create a quantum device instance. Args: - backend_name: Name of the quantum backend to use. - shots: Number of measurement shots. Defaults to SHOTS. - wires: Number of quantum wires (qubits). Defaults to WIRES. + backend_name (str): Name of the quantum backend to use. + shots (int): Number of measurement shots. + wires (int): Number of quantum wires (qubits). Returns: - qml.Device: Configured quantum device instance. + Device: Configured quantum device instance. """ return qml.device( "cqlib.device", @@ -48,24 +48,23 @@ def create_device( ) -def create_circuit( - device: Device, diff_method: str = "parameter-shift" -) -> qml.QNode: - """Creates a differentiable quantum circuit. +def create_circuit(device: Device, diff_method: str = "parameter-shift") -> qml.QNode: + """Create a differentiable quantum circuit. Args: - device: Quantum device to run the circuit on. - diff_method: Differentiation method to use. Defaults to "parameter-shift". + device (Device): Quantum device to run the circuit on. + diff_method (str): Differentiation method. Returns: - qml.QNode: Configured quantum circuit as a QNode. + qml.QNode: Configured quantum circuit. """ + @qml.qnode(device, diff_method=diff_method) def circuit(params: np.ndarray) -> float: - """Quantum circuit with parameterized rotations and measurement. + """Parameterized quantum circuit. Args: - params: Array of rotation parameters [theta_x, theta_y]. + params (np.ndarray): Rotation parameters [theta_x, theta_y]. Returns: float: Expectation value of PauliZ on wire 0. @@ -78,103 +77,57 @@ def create_circuit( return circuit -def test_backend(backend_name: str) -> None: - """Tests quantum computing functionality on a specific backend. - +@pytest.mark.parametrize("diff_method", ["parameter-shift", "finite-diff"]) +def test_gradient_computation(diff_method): + """Test that gradients can be computed using different methods. + Args: - backend_name: Name of the backend to test. - """ - print(f"\n=== Testing backend: {backend_name} ===") - - # Create device - device = create_device(backend_name) - - # Define probability measurement circuit - @qml.qnode(device) - def circuit_probs(params: np.ndarray) -> np.ndarray: - """Circuit for measuring probability distribution. - - Args: - params: Array of rotation parameters [theta_x, theta_y]. - - Returns: - np.ndarray: Probability distribution over computational basis states. - """ - qml.RX(params[0], wires=0) - qml.RY(params[1], wires=1) - qml.CNOT(wires=[0, 1]) - return qml.probs(wires=[0, 1]) - - # Define expectation value measurement circuit - @qml.qnode(device) - def circuit_expval(params: np.ndarray) -> float: - """Circuit for measuring expectation value. - - Args: - params: Array of rotation parameters [theta_x, theta_y]. - - Returns: - float: Expectation value of PauliZ on wire 0. - """ - qml.RX(params[0], wires=0) - qml.RY(params[1], wires=1) - qml.CNOT(wires=[0, 1]) - return qml.expval(qml.PauliZ(0)) - - # Test circuits - params = INITIAL_PARAMS.copy() - probabilities = circuit_probs(params) - expectation_value = circuit_expval(params) - - print(f"Probability distribution: {probabilities}") - print(f"Expectation value: {expectation_value:.6f}") - - -def optimize_circuit() -> None: - """Performs parameter optimization of the quantum circuit. - - Uses gradient descent optimization to minimize the expectation value - of the quantum circuit with respect to the rotation parameters. + diff_method (str): Differentiation method to test. """ - print("\n=== Starting parameter optimization ===") - - # Create default device and initialize circuit + device = create_device(DEFAULT_BACKEND) + circuit = create_circuit(device, diff_method=diff_method) + + # Test gradient computation + grad_fn = qml.grad(circuit) + gradient = grad_fn(INITIAL_PARAMS) + + # Assertions + assert gradient is not None + assert isinstance(gradient, np.ndarray) + assert gradient.shape == INITIAL_PARAMS.shape + + +def test_optimization_converges(): + """Test that gradient descent optimization reduces expectation value.""" device = create_device(DEFAULT_BACKEND) circuit = create_circuit(device) - # Initialize optimizer and parameters optimizer = qml.GradientDescentOptimizer(stepsize=STEP_SIZE) params = INITIAL_PARAMS.copy() + initial_expval = circuit(params) - # Execute optimization loop - for step in range(TRAINING_STEPS): + for _ in range(TRAINING_STEPS): params = optimizer.step(circuit, params) - expectation = circuit(params) - print( - f"Step {step + 1:2d}: " - f"params = [{params[0]:.6f}, {params[1]:.6f}], " - f"expectation = {expectation:.6f}" - ) + final_expval = circuit(params) - print("\n=== Optimization completed ===") - print(f"Final parameters: [{params[0]:.6f}, {params[1]:.6f}]") - print(f"Final expectation value: {circuit(params):.6f}") + # Assertion: final expectation value should not exceed the initial value + assert final_expval <= initial_expval + 1e-6 -def main() -> None: - """Main function to demonstrate quantum circuit functionality. - - Tests different quantum backends and performs parameter optimization. - """ - # Test different backends - test_backend("tianyan504") - test_backend("tianyan_sw") - test_backend("default") - - # Execute optimization - optimize_circuit() +def test_circuit_differentiability(): + """Test that the circuit is properly differentiable.""" + device = create_device(DEFAULT_BACKEND) + circuit = create_circuit(device) + + # Test that we can compute the gradient + try: + grad_fn = qml.grad(circuit) + gradient = grad_fn(INITIAL_PARAMS) + assert gradient is not None + except Exception as e: + pytest.fail(f"Circuit differentiation failed: {e}") if __name__ == "__main__": - main() \ No newline at end of file + pytest.main([__file__, "-v"]) \ No newline at end of file