This is an automated email from the ASF dual-hosted git repository.

guanmingchiu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/mahout.git


The following commit(s) were added to refs/heads/main by this push:
     new a9e1d825e MAHOUT-604: add a test_rotation_gates.py (#612)
a9e1d825e is described below

commit a9e1d825e7d1f5b89725e7978d2807893a6dc490
Author: GUAN-HAO HUANG <[email protected]>
AuthorDate: Sun Nov 16 00:26:10 2025 +0800

    MAHOUT-604: add a test_rotation_gates.py (#612)
---
 testing/test_rotation_gates.py | 453 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 453 insertions(+)

diff --git a/testing/test_rotation_gates.py b/testing/test_rotation_gates.py
new file mode 100644
index 000000000..36f50820b
--- /dev/null
+++ b/testing/test_rotation_gates.py
@@ -0,0 +1,453 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+
+import math
+
+import pytest
+
+from qumat import QuMat
+
+from .utils import TESTING_BACKENDS, get_backend_config
+
+
+def get_state_probability(results, target_state, num_qubits=1):
+    """Calculate the probability of measuring a target state."""
+    if isinstance(results, list):
+        results = results[0]
+
+    total_shots = sum(results.values())
+    if total_shots == 0:
+        return 0.0
+
+    if isinstance(target_state, str):
+        target_str = target_state
+        target_int = int(target_state, 2) if target_state else 0
+    else:
+        target_int = target_state
+        target_str = format(target_state, f"0{num_qubits}b")
+
+    target_count = 0
+    for state, count in results.items():
+        if isinstance(state, str):
+            if state == target_str:
+                target_count = count
+                break
+        else:
+            if state == target_int:
+                target_count = count
+                break
+
+    return target_count / total_shots
+
+
+def get_superposition_probabilities(results, num_qubits=1):
+    """Calculate probabilities for |0⟩ and |1⟩ states in a superposition."""
+    if isinstance(results, list):
+        results = results[0]
+
+    total_shots = sum(results.values())
+
+    zero_count = 0
+    one_count = 0
+    for state, count in results.items():
+        if isinstance(state, str):
+            if state == "0" * num_qubits:
+                zero_count = count
+            elif state == "1" * num_qubits:
+                one_count = count
+        else:
+            if state == 0:
+                zero_count = count
+            elif state == (2**num_qubits - 1):
+                one_count = count
+
+    prob_zero = zero_count / total_shots if total_shots > 0 else 0.0
+    prob_one = one_count / total_shots if total_shots > 0 else 0.0
+
+    return prob_zero, prob_one
+
+
[email protected]("backend_name", TESTING_BACKENDS)
+class TestRXGate:
+    """Test class for RX gate functionality."""
+
+    @pytest.mark.parametrize(
+        "angle, expected_behavior",
+        [
+            (0, "identity"),  # RX(0) = I
+            (math.pi, "pauli_x"),  # RX(π) = X
+            (math.pi / 2, "superposition"),  # RX(π/2) creates superposition
+            (2 * math.pi, "identity"),  # RX(2π) = I
+        ],
+    )
+    def test_rx_gate_with_different_angles(
+        self, backend_name, angle, expected_behavior
+    ):
+        """Test RX gate with different angles."""
+        backend_config = get_backend_config(backend_name)
+        qumat = QuMat(backend_config)
+        qumat.create_empty_circuit(num_qubits=1)
+
+        qumat.apply_rx_gate(0, angle)
+        results = qumat.execute_circuit()
+
+        if expected_behavior == "identity":
+            prob = get_state_probability(results, "0", num_qubits=1)
+            assert prob > 0.95, (
+                f"Backend: {backend_name}, "
+                f"Expected |0⟩ after RX({angle:.4f}), got probability 
{prob:.4f}"
+            )
+        elif expected_behavior == "pauli_x":
+            prob = get_state_probability(results, "1", num_qubits=1)
+            assert prob > 0.95, (
+                f"Backend: {backend_name}, "
+                f"Expected |1⟩ after RX({angle:.4f}), got probability 
{prob:.4f}"
+            )
+        elif expected_behavior == "superposition":
+            prob_zero, prob_one = get_superposition_probabilities(results, 
num_qubits=1)
+            assert 0.45 < prob_zero < 0.55, (
+                f"Expected ~0.5 probability for |0⟩ after RX({angle:.4f}), "
+                f"got {prob_zero:.4f}"
+            )
+            assert 0.45 < prob_one < 0.55, (
+                f"Expected ~0.5 probability for |1⟩ after RX({angle:.4f}), "
+                f"got {prob_one:.4f}"
+            )
+
+
[email protected]("backend_name", TESTING_BACKENDS)
+class TestRYGate:
+    """Test class for RY gate functionality."""
+
+    @pytest.mark.parametrize(
+        "angle, expected_behavior",
+        [
+            (0, "identity"),  # RY(0) = I
+            (math.pi, "pauli_y"),  # RY(π) ≈ Y (phase doesn't affect 
measurement)
+            (math.pi / 2, "superposition"),  # RY(π/2) creates superposition
+            (2 * math.pi, "identity"),  # RY(2π) = I
+        ],
+    )
+    def test_ry_gate_with_different_angles(
+        self, backend_name, angle, expected_behavior
+    ):
+        """Test RY gate with different angles."""
+        backend_config = get_backend_config(backend_name)
+        qumat = QuMat(backend_config)
+        qumat.create_empty_circuit(num_qubits=1)
+
+        qumat.apply_ry_gate(0, angle)
+        results = qumat.execute_circuit()
+
+        if expected_behavior == "identity":
+            prob = get_state_probability(results, "0", num_qubits=1)
+            assert prob > 0.95, (
+                f"Backend: {backend_name}, "
+                f"Expected |0⟩ after RY({angle:.4f}), got probability 
{prob:.4f}"
+            )
+        elif expected_behavior == "pauli_y":
+            # RY(π) flips |0⟩ to |1⟩ (like Y gate, phase doesn't affect 
measurement)
+            prob = get_state_probability(results, "1", num_qubits=1)
+            assert prob > 0.95, (
+                f"Backend: {backend_name}, "
+                f"Expected |1⟩ after RY({angle:.4f}), got probability 
{prob:.4f}"
+            )
+        elif expected_behavior == "superposition":
+            prob_zero, prob_one = get_superposition_probabilities(results, 
num_qubits=1)
+            assert 0.45 < prob_zero < 0.55, (
+                f"Expected ~0.5 probability for |0⟩ after RY({angle:.4f}), "
+                f"got {prob_zero:.4f}"
+            )
+            assert 0.45 < prob_one < 0.55, (
+                f"Expected ~0.5 probability for |1⟩ after RY({angle:.4f}), "
+                f"got {prob_one:.4f}"
+            )
+
+
[email protected]("backend_name", TESTING_BACKENDS)
+class TestRZGate:
+    """Test class for RZ gate functionality."""
+
+    @pytest.mark.parametrize(
+        "angle, expected_state",
+        [
+            (0, "0"),  # RZ(0) = I, |0⟩ -> |0⟩
+            (math.pi, "0"),  # RZ(π) adds phase, but |0⟩ measurement unchanged
+            (2 * math.pi, "0"),  # RZ(2π) = I
+        ],
+    )
+    def test_rz_gate_with_different_angles(self, backend_name, angle, 
expected_state):
+        """Test RZ gate with different angles."""
+        backend_config = get_backend_config(backend_name)
+        qumat = QuMat(backend_config)
+        qumat.create_empty_circuit(num_qubits=1)
+
+        qumat.apply_rz_gate(0, angle)
+        results = qumat.execute_circuit()
+
+        # RZ only affects phase, not measurement probability for |0⟩
+        prob = get_state_probability(results, expected_state, num_qubits=1)
+        assert prob > 0.95, (
+            f"Backend: {backend_name}, "
+            f"Expected |{expected_state}⟩ after RZ({angle:.4f}), got 
probability {prob:.4f}"
+        )
+
+    def test_rz_gate_phase_effect(self, backend_name):
+        """Test RZ gate phase effect using Hadamard."""
+        backend_config = get_backend_config(backend_name)
+        qumat = QuMat(backend_config)
+        qumat.create_empty_circuit(num_qubits=1)
+
+        # H -> RZ(π) -> H should flip |0⟩ to |1⟩
+        qumat.apply_hadamard_gate(0)  # |0⟩ -> |+⟩
+        qumat.apply_rz_gate(0, math.pi)  # |+⟩ -> |-⟩
+        qumat.apply_hadamard_gate(0)  # |-⟩ -> |1⟩
+
+        results = qumat.execute_circuit()
+        prob = get_state_probability(results, "1", num_qubits=1)
+
+        assert prob > 0.95, (
+            f"Backend: {backend_name}, "
+            f"Expected |1⟩ after H-RZ(π)-H, got probability {prob:.4f}"
+        )
+
+
[email protected]("backend_name", TESTING_BACKENDS)
+class TestParameterizedRotationGates:
+    """Test class for parameterized rotation gates using string parameters."""
+
+    @pytest.mark.parametrize(
+        "gate_type, param_name",
+        [
+            ("rx", "theta"),
+            ("ry", "phi"),
+            ("rz", "lambda"),
+        ],
+    )
+    def test_parameterized_rotation_gate(self, backend_name, gate_type, 
param_name):
+        """Test parameterized rotation gates with string parameters."""
+        backend_config = get_backend_config(backend_name)
+        qumat = QuMat(backend_config)
+        qumat.create_empty_circuit(num_qubits=1)
+
+        # Apply parameterized gate
+        if gate_type == "rx":
+            qumat.apply_rx_gate(0, param_name)
+        elif gate_type == "ry":
+            qumat.apply_ry_gate(0, param_name)
+        elif gate_type == "rz":
+            qumat.apply_rz_gate(0, param_name)
+
+        # Verify parameter was registered
+        assert param_name in qumat.parameters, (
+            f"Parameter '{param_name}' should be registered in circuit"
+        )
+
+
[email protected]("backend_name", TESTING_BACKENDS)
+class TestParameterBinding:
+    """Test class for parameter binding in rotation gates."""
+
+    @pytest.mark.parametrize(
+        "gate_type, param_name, bound_value, expected_behavior",
+        [
+            ("rx", "theta", math.pi, "pauli_x"),  # RX(π) = X
+            (
+                "rx",
+                "theta",
+                math.pi / 2,
+                "superposition",
+            ),  # RX(π/2) creates superposition
+            (
+                "ry",
+                "phi",
+                math.pi / 2,
+                "superposition",
+            ),  # RY(π/2) creates superposition
+            (
+                "rz",
+                "lambda",
+                math.pi,
+                "identity",
+            ),  # RZ(π) doesn't change |0⟩ measurement
+        ],
+    )
+    def test_parameter_binding(
+        self,
+        backend_name,
+        gate_type,
+        param_name,
+        bound_value,
+        expected_behavior,
+    ):
+        """Test parameter binding for rotation gates."""
+        backend_config = get_backend_config(backend_name)
+        qumat = QuMat(backend_config)
+        qumat.create_empty_circuit(num_qubits=1)
+
+        # Apply parameterized gate
+        if gate_type == "rx":
+            qumat.apply_rx_gate(0, param_name)
+        elif gate_type == "ry":
+            qumat.apply_ry_gate(0, param_name)
+        elif gate_type == "rz":
+            qumat.apply_rz_gate(0, param_name)
+
+        # Execute circuit with parameter values
+        results = qumat.execute_circuit(parameter_values={param_name: 
bound_value})
+
+        if expected_behavior == "pauli_x":
+            prob = get_state_probability(results, "1", num_qubits=1)
+            assert prob > 0.95, (
+                f"Backend: {backend_name}, "
+                f"Expected |1⟩ after binding {param_name}={bound_value:.4f}, "
+                f"got probability {prob:.4f}"
+            )
+        elif expected_behavior == "superposition":
+            prob_zero, prob_one = get_superposition_probabilities(results, 
num_qubits=1)
+            assert 0.45 < prob_zero < 0.55, (
+                f"Expected ~0.5 probability for |0⟩ after binding 
{param_name}={bound_value:.4f}, "
+                f"got {prob_zero:.4f}"
+            )
+            assert 0.45 < prob_one < 0.55, (
+                f"Expected ~0.5 probability for |1⟩ after binding 
{param_name}={bound_value:.4f}, "
+                f"got {prob_one:.4f}"
+            )
+        elif expected_behavior == "identity":
+            prob = get_state_probability(results, "0", num_qubits=1)
+            assert prob > 0.95, (
+                f"Backend: {backend_name}, "
+                f"Expected |0⟩ after binding {param_name}={bound_value:.4f}, "
+                f"got probability {prob:.4f}"
+            )
+
+    def test_multiple_parameter_binding(self, backend_name):
+        """Test binding multiple parameters."""
+        backend_config = get_backend_config(backend_name)
+        qumat = QuMat(backend_config)
+        qumat.create_empty_circuit(num_qubits=2)
+
+        # Apply different parameterized gates to different qubits
+        qumat.apply_rx_gate(0, "theta0")
+        qumat.apply_ry_gate(1, "phi1")
+
+        # Execute circuit with multiple parameter values
+        results = qumat.execute_circuit(
+            parameter_values={"theta0": math.pi, "phi1": math.pi / 2}
+        )
+
+        # Qubit 0 should be |1⟩ (RX(π) = X)
+        # Qubit 1 should be in superposition (RY(π/2))
+        # Check that we get expected states
+        prob_one_zero = get_state_probability(results, "10", num_qubits=2)
+        prob_one_one = get_state_probability(results, "11", num_qubits=2)
+
+        # At least one of these should have significant probability
+        total_prob = prob_one_zero + prob_one_one
+        assert total_prob > 0.4, (
+            f"Expected high probability for states with qubit 0=|1⟩, "
+            f"got {total_prob:.4f}"
+        )
+
+    def test_invalid_parameter_binding(self, backend_name):
+        """Test that binding invalid parameter raises error."""
+        backend_config = get_backend_config(backend_name)
+        qumat = QuMat(backend_config)
+        qumat.create_empty_circuit(num_qubits=1)
+
+        qumat.apply_rx_gate(0, "theta")
+
+        # Try to bind non-existent parameter
+        with pytest.raises(ValueError, match="parameter 'invalid_param' not 
found"):
+            qumat.bind_parameters({"invalid_param": math.pi})
+
+
[email protected]("backend_name", TESTING_BACKENDS)
+class TestRotationGatesEdgeCases:
+    """Test class for edge cases of rotation gates."""
+
+    def test_rotation_gate_on_uninitialized_circuit(self, backend_name):
+        """Test that rotation gates raise error on uninitialized circuit."""
+        backend_config = get_backend_config(backend_name)
+        qumat = QuMat(backend_config)
+
+        with pytest.raises(RuntimeError, match="circuit not initialized"):
+            qumat.apply_rx_gate(0, math.pi)
+
+    def test_rotation_gate_with_negative_angle(self, backend_name):
+        """Test rotation gates with negative angles."""
+        backend_config = get_backend_config(backend_name)
+        qumat = QuMat(backend_config)
+        qumat.create_empty_circuit(num_qubits=1)
+
+        # RX(-π) should be equivalent to RX(π) = X
+        qumat.apply_rx_gate(0, -math.pi)
+        results = qumat.execute_circuit()
+
+        prob = get_state_probability(results, "1", num_qubits=1)
+        assert prob > 0.95, (
+            f"Backend: {backend_name}, "
+            f"Expected |1⟩ after RX(-π), got probability {prob:.4f}"
+        )
+
+    def test_rotation_gate_with_large_angle(self, backend_name):
+        """Test rotation gates with large angles."""
+        backend_config = get_backend_config(backend_name)
+        qumat = QuMat(backend_config)
+        qumat.create_empty_circuit(num_qubits=1)
+
+        # RX(4π) = RX(0) = I
+        qumat.apply_rx_gate(0, 4 * math.pi)
+        results = qumat.execute_circuit()
+
+        prob = get_state_probability(results, "0", num_qubits=1)
+        assert prob > 0.95, (
+            f"Backend: {backend_name}, "
+            f"Expected |0⟩ after RX(4π), got probability {prob:.4f}"
+        )
+
+    def test_multiple_rotations_on_same_qubit(self, backend_name):
+        """Test applying multiple rotations sequentially on the same qubit."""
+        backend_config = get_backend_config(backend_name)
+        qumat = QuMat(backend_config)
+        qumat.create_empty_circuit(num_qubits=1)
+
+        # RX(π/2) -> RY(π/2) -> RZ(π/2) sequence
+        qumat.apply_rx_gate(0, math.pi / 2)  # Creates superposition
+        qumat.apply_ry_gate(0, math.pi / 2)  # Rotates superposition
+        qumat.apply_rz_gate(0, math.pi / 2)  # Adds phase
+
+        results = qumat.execute_circuit()
+
+        # Should still be in a valid quantum state
+        total_shots = (
+            sum(results.values())
+            if isinstance(results, dict)
+            else sum(results[0].values())
+        )
+        assert total_shots > 0, "Circuit execution should produce results"
+
+    def test_rotation_gate_on_invalid_qubit_index(self, backend_name):
+        """Test rotation gates with invalid qubit index."""
+        backend_config = get_backend_config(backend_name)
+        qumat = QuMat(backend_config)
+        qumat.create_empty_circuit(num_qubits=2)
+
+        try:
+            qumat.apply_rx_gate(5, math.pi)  # Invalid index
+        except (IndexError, ValueError, RuntimeError, Exception):
+            pass

Reply via email to