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 f210fa43f MAHOUT-604: add test_multi_qubit_gates.py - part 1 (#629)
f210fa43f is described below
commit f210fa43f572e6104ee07b2506acacb9b387e606
Author: GUAN-HAO HUANG <[email protected]>
AuthorDate: Wed Nov 26 10:38:30 2025 +0800
MAHOUT-604: add test_multi_qubit_gates.py - part 1 (#629)
* MAHOUT-604: add test_multi_qubit_gates.py - part 1
* follow the DRY and fix lint
* DRY
---------
Co-authored-by: rich <[email protected]>
---
testing/test_multi_qubit_gates.py | 492 +++++++++++++++++++++++++++++++++++++
testing/test_single_qubit_gates.py | 90 +++----
testing/utils/__init__.py | 4 +-
testing/utils/qumat_helpers.py | 67 +++++
4 files changed, 596 insertions(+), 57 deletions(-)
diff --git a/testing/test_multi_qubit_gates.py
b/testing/test_multi_qubit_gates.py
new file mode 100644
index 000000000..9c5febbd6
--- /dev/null
+++ b/testing/test_multi_qubit_gates.py
@@ -0,0 +1,492 @@
+#
+# 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 pytest
+
+from .utils import TESTING_BACKENDS, get_backend_config, get_state_probability
+from qumat import QuMat
+
+
+def create_qumat_instance(backend_name, num_qubits):
+ """Create and initialize a QuMat instance with a circuit."""
+ backend_config = get_backend_config(backend_name)
+ qumat = QuMat(backend_config)
+ qumat.create_empty_circuit(num_qubits=num_qubits)
+ return qumat
+
+
+def prepare_initial_state(qumat, initial_state_str):
+ """
+ Prepare initial state by applying X gates to qubits that should be |1⟩.
+
+ Args:
+ qumat: QuMat instance
+ initial_state_str: Binary string representing initial state (e.g.,
"101")
+ """
+ for i, bit in enumerate(initial_state_str):
+ if bit == "1":
+ qumat.apply_pauli_x_gate(i)
+
+
+def execute_and_assert_state(
+ qumat, expected_state, num_qubits, backend_name, threshold=0.95,
context_msg=""
+):
+ """
+ Execute circuit and assert expected state probability.
+
+ Args:
+ qumat: QuMat instance
+ expected_state: Expected state string or int
+ num_qubits: Number of qubits
+ backend_name: Backend name
+ threshold: Probability threshold (default 0.95)
+ context_msg: Additional context message for assertion
+ """
+ results = qumat.execute_circuit()
+ prob = get_state_probability(results, expected_state, num_qubits,
backend_name)
+ assert prob > threshold, (
+ f"Backend: {backend_name}, {context_msg}, "
+ f"Expected: |{expected_state}⟩, Got probability: {prob:.4f}"
+ )
+ return prob
+
+
[email protected]("backend_name", TESTING_BACKENDS)
+class TestCNOTGate:
+ """Test class for CNOT gate functionality."""
+
+ @pytest.mark.parametrize(
+ "initial_state, control_qubit, target_qubit, expected_state",
+ [
+ ("00", 0, 1, "00"), # |00⟩ -> CNOT(0,1) -> |00⟩ (control=0, no
flip)
+ ("01", 0, 1, "01"), # |01⟩ -> CNOT(0,1) -> |01⟩ (control=0, no
flip)
+ ("10", 0, 1, "11"), # |10⟩ -> CNOT(0,1) -> |11⟩ (control=1, flip
target)
+ ("11", 0, 1, "10"), # |11⟩ -> CNOT(0,1) -> |10⟩ (control=1, flip
target)
+ ("00", 1, 0, "00"), # |00⟩ -> CNOT(1,0) -> |00⟩ (control=0, no
flip)
+ ("01", 1, 0, "11"), # |01⟩ -> CNOT(1,0) -> |11⟩ (control=1, flip
target)
+ ("10", 1, 0, "10"), # |10⟩ -> CNOT(1,0) -> |10⟩ (control=0, no
flip)
+ ("11", 1, 0, "01"), # |11⟩ -> CNOT(1,0) -> |01⟩ (control=1, flip
target)
+ ],
+ )
+ def test_cnot_state_transitions(
+ self, backend_name, initial_state, control_qubit, target_qubit,
expected_state
+ ):
+ """Test CNOT gate state transitions with parametrized test cases."""
+ qumat = create_qumat_instance(backend_name, num_qubits=2)
+ prepare_initial_state(qumat, initial_state)
+ qumat.apply_cnot_gate(control_qubit, target_qubit)
+ execute_and_assert_state(
+ qumat,
+ expected_state,
+ num_qubits=2,
+ backend_name=backend_name,
+ context_msg=(
+ f"Initial state: |{initial_state}⟩, "
+ f"CNOT(control={control_qubit}, target={target_qubit})"
+ ),
+ )
+
+ @pytest.mark.parametrize(
+ "initial_state, num_applications, expected_state",
+ [
+ ("00", 1, "00"), # |00⟩ -> CNOT -> |00⟩
+ ("00", 2, "00"), # |00⟩ -> CNOT -> CNOT -> |00⟩ (CNOT² = I)
+ ("10", 1, "11"), # |10⟩ -> CNOT -> |11⟩
+ ("10", 2, "10"), # |10⟩ -> CNOT -> |11⟩ -> CNOT -> |10⟩ (CNOT² =
I)
+ ("11", 1, "10"), # |11⟩ -> CNOT -> |10⟩
+ ("11", 2, "11"), # |11⟩ -> CNOT -> |10⟩ -> CNOT -> |11⟩ (CNOT² =
I)
+ ],
+ )
+ def test_cnot_double_application(
+ self, backend_name, initial_state, num_applications, expected_state
+ ):
+ """Test that applying CNOT twice returns to original state (CNOT² =
I)."""
+ qumat = create_qumat_instance(backend_name, num_qubits=2)
+ prepare_initial_state(qumat, initial_state)
+ for _ in range(num_applications):
+ qumat.apply_cnot_gate(0, 1)
+ execute_and_assert_state(
+ qumat,
+ expected_state,
+ num_qubits=2,
+ backend_name=backend_name,
+ context_msg=(
+ f"Initial state: |{initial_state}⟩, "
+ f"CNOT applications: {num_applications}"
+ ),
+ )
+
+ @pytest.mark.parametrize(
+ "control_qubit, target_qubit, num_qubits",
+ [
+ # 3-qubit circuits
+ (0, 1, 3), # CNOT on qubits 0 and 1 in 3-qubit circuit
+ (1, 2, 3), # CNOT on qubits 1 and 2 in 3-qubit circuit
+ (0, 2, 3), # CNOT on qubits 0 and 2 in 3-qubit circuit
+ # 4-qubit circuits
+ (0, 1, 4), # CNOT on qubits 0 and 1 in 4-qubit circuit
+ (0, 3, 4), # CNOT on qubits 0 and 3 in 4-qubit circuit
+ (1, 2, 4), # CNOT on qubits 1 and 2 in 4-qubit circuit
+ (2, 3, 4), # CNOT on qubits 2 and 3 in 4-qubit circuit
+ # 5-qubit circuits
+ (0, 4, 5), # CNOT on qubits 0 and 4 in 5-qubit circuit
+ (2, 3, 5), # CNOT on qubits 2 and 3 in 5-qubit circuit
+ ],
+ )
+ def test_cnot_on_multiple_qubits(
+ self, backend_name, control_qubit, target_qubit, num_qubits
+ ):
+ """Test CNOT gate on different qubit pairs in multi-qubit circuits."""
+ qumat = create_qumat_instance(backend_name, num_qubits=num_qubits)
+ qumat.apply_pauli_x_gate(control_qubit)
+ qumat.apply_cnot_gate(control_qubit, target_qubit)
+ # Expected: control and target qubits should both be |1⟩
+ expected_state = "".join(
+ "1" if i in (control_qubit, target_qubit) else "0"
+ for i in range(num_qubits)
+ )
+ execute_and_assert_state(
+ qumat,
+ expected_state,
+ num_qubits=num_qubits,
+ backend_name=backend_name,
+ context_msg=(
+ f"CNOT(control={control_qubit}, target={target_qubit}) "
+ f"on {num_qubits}-qubit circuit"
+ ),
+ )
+
+ @pytest.mark.parametrize(
+ "control_qubit, target_qubit, expected_states",
+ [
+ # Standard Bell state: |00⟩ + |11⟩
+ (0, 1, ["00", "11"]),
+ # Reversed control/target: |00⟩ + |11⟩ (same result)
+ (1, 0, ["00", "11"]),
+ ],
+ )
+ def test_cnot_entanglement(
+ self, backend_name, control_qubit, target_qubit, expected_states
+ ):
+ """Test that CNOT gate creates entanglement (Bell state) with
parametrized test cases."""
+ qumat = create_qumat_instance(backend_name, num_qubits=2)
+ qumat.apply_hadamard_gate(control_qubit)
+ qumat.apply_cnot_gate(control_qubit, target_qubit)
+ results = qumat.execute_circuit()
+ # Should measure expected states with approximately equal probability
+ for expected_state in expected_states:
+ prob = get_state_probability(
+ results, expected_state, num_qubits=2,
backend_name=backend_name
+ )
+ assert 0.45 < prob < 0.55, (
+ f"Backend: {backend_name}, "
+ f"CNOT(control={control_qubit}, target={target_qubit}), "
+ f"Expected ~0.5 probability for |{expected_state}⟩ in Bell
state, got {prob:.4f}"
+ )
+
+
[email protected]("backend_name", TESTING_BACKENDS)
+class TestToffoliGate:
+ """Test class for Toffoli gate functionality."""
+
+ @pytest.mark.parametrize(
+ "initial_state, control1, control2, target, expected_state",
+ [
+ # Toffoli(0,1,2): flip target only if both controls are |1⟩
+ ("000", 0, 1, 2, "000"), # |000⟩ -> Toffoli -> |000⟩
+ ("001", 0, 1, 2, "001"), # |001⟩ -> Toffoli -> |001⟩
+ ("010", 0, 1, 2, "010"), # |010⟩ -> Toffoli -> |010⟩
+ ("011", 0, 1, 2, "011"), # |011⟩ -> Toffoli -> |011⟩
+ ("100", 0, 1, 2, "100"), # |100⟩ -> Toffoli -> |100⟩
+ ("101", 0, 1, 2, "101"), # |101⟩ -> Toffoli -> |101⟩
+ (
+ "110",
+ 0,
+ 1,
+ 2,
+ "111",
+ ), # |110⟩ -> Toffoli -> |111⟩ (both controls=1, flip target)
+ (
+ "111",
+ 0,
+ 1,
+ 2,
+ "110",
+ ), # |111⟩ -> Toffoli -> |110⟩ (both controls=1, flip target)
+ # Different control/target combinations
+ # |110⟩ -> Toffoli(0,2,1): control0=1, control2=0 -> no flip,
result |110⟩
+ (
+ "110",
+ 0,
+ 2,
+ 1,
+ "110",
+ ), # |110⟩ -> Toffoli(0,2,1) -> |110⟩ (control2=0, no flip)
+ # |101⟩ -> Toffoli(1,2,0): control1=0, control2=1 -> no flip,
result |101⟩
+ (
+ "101",
+ 1,
+ 2,
+ 0,
+ "101",
+ ), # |101⟩ -> Toffoli(1,2,0) -> |101⟩ (control1=0, no flip)
+ # Test cases where both controls are 1 with different target
+ (
+ "111",
+ 0,
+ 2,
+ 1,
+ "101",
+ ), # |111⟩ -> Toffoli(0,2,1) -> |101⟩ (both controls=1, flip
target)
+ (
+ "111",
+ 1,
+ 2,
+ 0,
+ "011",
+ ), # |111⟩ -> Toffoli(1,2,0) -> |011⟩ (both controls=1, flip
target)
+ ],
+ )
+ def test_toffoli_state_transitions(
+ self,
+ backend_name,
+ initial_state,
+ control1,
+ control2,
+ target,
+ expected_state,
+ ):
+ """Test Toffoli gate state transitions with parametrized test cases."""
+ qumat = create_qumat_instance(backend_name, num_qubits=3)
+ prepare_initial_state(qumat, initial_state)
+ qumat.apply_toffoli_gate(control1, control2, target)
+ execute_and_assert_state(
+ qumat,
+ expected_state,
+ num_qubits=3,
+ backend_name=backend_name,
+ context_msg=(
+ f"Initial state: |{initial_state}⟩, "
+ f"Toffoli(control1={control1}, control2={control2},
target={target})"
+ ),
+ )
+
+ @pytest.mark.parametrize(
+ "initial_state, num_applications, expected_state",
+ [
+ ("000", 1, "000"), # |000⟩ -> Toffoli -> |000⟩
+ ("000", 2, "000"), # |000⟩ -> Toffoli -> Toffoli -> |000⟩
(Toffoli² = I)
+ ("110", 1, "111"), # |110⟩ -> Toffoli -> |111⟩
+ (
+ "110",
+ 2,
+ "110",
+ ), # |110⟩ -> Toffoli -> |111⟩ -> Toffoli -> |110⟩ (Toffoli² = I)
+ ("111", 1, "110"), # |111⟩ -> Toffoli -> |110⟩
+ (
+ "111",
+ 2,
+ "111",
+ ), # |111⟩ -> Toffoli -> |110⟩ -> Toffoli -> |111⟩ (Toffoli² = I)
+ ],
+ )
+ def test_toffoli_double_application(
+ self, backend_name, initial_state, num_applications, expected_state
+ ):
+ """Test that applying Toffoli twice returns to original state
(Toffoli² = I)."""
+ qumat = create_qumat_instance(backend_name, num_qubits=3)
+ prepare_initial_state(qumat, initial_state)
+ for _ in range(num_applications):
+ qumat.apply_toffoli_gate(0, 1, 2)
+ execute_and_assert_state(
+ qumat,
+ expected_state,
+ num_qubits=3,
+ backend_name=backend_name,
+ context_msg=(
+ f"Initial state: |{initial_state}⟩, "
+ f"Toffoli applications: {num_applications}"
+ ),
+ )
+
+ @pytest.mark.parametrize(
+ "control1, control2, target, num_qubits",
+ [
+ # 4-qubit circuits
+ (0, 1, 2, 4), # Toffoli on qubits 0, 1, 2 in 4-qubit circuit
+ (0, 1, 3, 4), # Toffoli on qubits 0, 1, 3 in 4-qubit circuit
+ (1, 2, 3, 4), # Toffoli on qubits 1, 2, 3 in 4-qubit circuit
+ (0, 2, 3, 4), # Toffoli on qubits 0, 2, 3 in 4-qubit circuit
+ # 5-qubit circuits
+ (0, 1, 2, 5), # Toffoli on qubits 0, 1, 2 in 5-qubit circuit
+ (0, 1, 4, 5), # Toffoli on qubits 0, 1, 4 in 5-qubit circuit
+ (2, 3, 4, 5), # Toffoli on qubits 2, 3, 4 in 5-qubit circuit
+ ],
+ )
+ def test_toffoli_on_multiple_qubits(
+ self, backend_name, control1, control2, target, num_qubits
+ ):
+ """Test Toffoli gate on different qubit combinations in multi-qubit
circuits."""
+ qumat = create_qumat_instance(backend_name, num_qubits=num_qubits)
+ qumat.apply_pauli_x_gate(control1)
+ qumat.apply_pauli_x_gate(control2)
+ qumat.apply_toffoli_gate(control1, control2, target)
+ # Expected: all three qubits should be |1⟩
+ expected_state = "".join(
+ "1" if i in (control1, control2, target) else "0" for i in
range(num_qubits)
+ )
+ execute_and_assert_state(
+ qumat,
+ expected_state,
+ num_qubits=num_qubits,
+ backend_name=backend_name,
+ context_msg=(
+ f"Toffoli(control1={control1}, control2={control2},
target={target}) "
+ f"on {num_qubits}-qubit circuit"
+ ),
+ )
+
+ @pytest.mark.parametrize(
+ "initial_state, expected_state, control1, control2, target",
+ [
+ # Toffoli(0,1,2): target = control0 AND control1
+ ("000", "000", 0, 1, 2), # 0 AND 0 = 0
+ ("010", "010", 0, 1, 2), # 0 AND 1 = 0
+ ("100", "100", 0, 1, 2), # 1 AND 0 = 0
+ ("110", "111", 0, 1, 2), # 1 AND 1 = 1 (target flips)
+ # Toffoli(0,2,1): target = control0 AND control2
+ ("000", "000", 0, 2, 1), # 0 AND 0 = 0
+ ("001", "001", 0, 2, 1), # 0 AND 1 = 0
+ ("100", "100", 0, 2, 1), # 1 AND 0 = 0
+ ("101", "111", 0, 2, 1), # 1 AND 1 = 1 (target flips)
+ # Toffoli(1,2,0): target = control1 AND control2
+ ("000", "000", 1, 2, 0), # 0 AND 0 = 0
+ ("001", "001", 1, 2, 0), # 0 AND 1 = 0
+ ("010", "010", 1, 2, 0), # 1 AND 0 = 0
+ ("011", "111", 1, 2, 0), # 1 AND 1 = 1 (target flips)
+ ],
+ )
+ def test_toffoli_and_gate_behavior(
+ self, backend_name, initial_state, expected_state, control1, control2,
target
+ ):
+ """Test that Toffoli gate acts as a quantum AND gate with parametrized
test cases."""
+ qumat = create_qumat_instance(backend_name, num_qubits=3)
+ prepare_initial_state(qumat, initial_state)
+ qumat.apply_toffoli_gate(control1, control2, target)
+ execute_and_assert_state(
+ qumat,
+ expected_state,
+ num_qubits=3,
+ backend_name=backend_name,
+ context_msg=(
+ f"Toffoli AND gate: |{initial_state}⟩ -> "
+ f"Toffoli({control1},{control2},{target}) ->
|{expected_state}⟩"
+ ),
+ )
+
+
[email protected]("backend_name", TESTING_BACKENDS)
+class TestMultiQubitGatesEdgeCases:
+ """Test class for edge cases of multi-qubit gates."""
+
+ @pytest.mark.parametrize(
+ "gate_name, gate_args",
+ [
+ ("cnot", (0, 1)),
+ ("toffoli", (0, 1, 2)),
+ ],
+ )
+ def test_gate_on_uninitialized_circuit(self, backend_name, gate_name,
gate_args):
+ """Test that 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"):
+ if gate_name == "cnot":
+ qumat.apply_cnot_gate(*gate_args)
+ else:
+ qumat.apply_toffoli_gate(*gate_args)
+
+ @pytest.mark.parametrize(
+ "num_qubits, gate_name, gate_args",
+ [
+ (2, "cnot", (5, 6)),
+ (3, "cnot", (10, 11)),
+ (4, "cnot", (5, 6)),
+ (3, "toffoli", (5, 6, 7)),
+ (4, "toffoli", (10, 11, 12)),
+ (5, "toffoli", (6, 7, 8)),
+ ],
+ )
+ def test_gate_with_invalid_qubit_indices(
+ self, backend_name, num_qubits, gate_name, gate_args
+ ):
+ """Test that gates handle invalid qubit indices appropriately."""
+ qumat = create_qumat_instance(backend_name, num_qubits=num_qubits)
+ try:
+ if gate_name == "cnot":
+ qumat.apply_cnot_gate(*gate_args)
+ else:
+ qumat.apply_toffoli_gate(*gate_args)
+ except (IndexError, ValueError, RuntimeError, Exception):
+ pass
+
+ @pytest.mark.parametrize(
+ "num_qubits, gate_name, gate_args",
+ [
+ (2, "cnot", (0, 0)),
+ (3, "cnot", (1, 1)),
+ (4, "cnot", (2, 2)),
+ (3, "toffoli", (0, 0, 0)),
+ (4, "toffoli", (1, 1, 1)),
+ (5, "toffoli", (2, 2, 2)),
+ (3, "toffoli", (0, 1, 0)),
+ (3, "toffoli", (0, 0, 1)),
+ ],
+ )
+ def test_gate_with_same_qubits(
+ self, backend_name, num_qubits, gate_name, gate_args
+ ):
+ """Test gates with same qubits (should raise error or handle
gracefully)."""
+ qumat = create_qumat_instance(backend_name, num_qubits=num_qubits)
+ try:
+ if gate_name == "cnot":
+ qumat.apply_cnot_gate(*gate_args)
+ else:
+ qumat.apply_toffoli_gate(*gate_args)
+ results = qumat.execute_circuit()
+ assert results is not None
+ except (ValueError, RuntimeError, Exception):
+ pass
+
+ def test_cnot_cross_backend_consistency(self, backend_name):
+ """Test that CNOT gate produces consistent results across all
backends."""
+ results_dict = {}
+ for backend in TESTING_BACKENDS:
+ qumat = create_qumat_instance(backend, num_qubits=2)
+ qumat.apply_pauli_x_gate(0)
+ qumat.apply_cnot_gate(0, 1)
+ results = qumat.execute_circuit()
+ results_dict[backend] = get_state_probability(
+ results, "11", num_qubits=2, backend_name=backend
+ )
+ # All backends should give similar results (within 5% tolerance)
+ probabilities = list(results_dict.values())
+ for i in range(len(probabilities)):
+ for j in range(i + 1, len(probabilities)):
+ assert abs(probabilities[i] - probabilities[j]) < 0.05, (
+ f"Backends have inconsistent CNOT results: {results_dict}"
+ )
diff --git a/testing/test_single_qubit_gates.py
b/testing/test_single_qubit_gates.py
index 3297bce86..9b66ab4da 100644
--- a/testing/test_single_qubit_gates.py
+++ b/testing/test_single_qubit_gates.py
@@ -18,53 +18,10 @@
import math
import pytest
-from .utils import TESTING_BACKENDS, get_backend_config
+from .utils import TESTING_BACKENDS, get_backend_config, get_state_probability
from qumat import QuMat
-def get_state_probability(results, target_state, num_qubits=1):
- """
- Calculate the probability of measuring a target state.
-
- Args:
- results: Dictionary of measurement results from execute_circuit()
- target_state: Target state as string (e.g., "0", "1", "101") or int
- num_qubits: Number of qubits in the circuit
-
- Returns:
- Probability of measuring the target state
- """
- if isinstance(results, list):
- results = results[0]
-
- total_shots = sum(results.values())
- if total_shots == 0:
- return 0.0
-
- # Convert target_state to both string and int formats for comparison
- if isinstance(target_state, str):
- target_str = target_state
- # Convert binary string to integer
- target_int = int(target_state, 2) if target_state else 0
- else:
- target_int = target_state
- # Convert integer to binary string
- 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.
@@ -135,7 +92,9 @@ class TestPauliXGate:
results = qumat.execute_circuit()
# Calculate probability of expected state
- prob = get_state_probability(results, expected_state, num_qubits=1)
+ prob = get_state_probability(
+ results, expected_state, num_qubits=1, backend_name=backend_name
+ )
assert prob > 0.95, (
f"Backend: {backend_name}, "
@@ -170,7 +129,9 @@ class TestPauliXGate:
results = qumat.execute_circuit()
# Calculate probability of expected state
- prob = get_state_probability(results, expected_state,
num_qubits=num_qubits)
+ prob = get_state_probability(
+ results, expected_state, num_qubits=num_qubits,
backend_name=backend_name
+ )
assert prob > 0.95, (
f"Backend: {backend_name}, "
@@ -213,7 +174,9 @@ class TestPauliYGate:
results = qumat.execute_circuit()
# Calculate probability of expected state
- prob = get_state_probability(results, expected_state, num_qubits=1)
+ prob = get_state_probability(
+ results, expected_state, num_qubits=1, backend_name=backend_name
+ )
assert prob > 0.95, (
f"Backend: {backend_name}, "
@@ -270,7 +233,9 @@ class TestHadamardGate:
)
else:
# Double application returns to original state
- prob = get_state_probability(results, initial_state, num_qubits=1)
+ prob = get_state_probability(
+ results, initial_state, num_qubits=1, backend_name=backend_name
+ )
assert prob > 0.95, (
f"Backend: {backend_name}, "
f"Initial state: |{initial_state}⟩, "
@@ -314,7 +279,9 @@ class TestNOTGate:
results = qumat.execute_circuit()
# Calculate probability of expected state
- prob = get_state_probability(results, expected_state, num_qubits=1)
+ prob = get_state_probability(
+ results, expected_state, num_qubits=1, backend_name=backend_name
+ )
assert prob > 0.95, (
f"Backend: {backend_name}, "
@@ -394,14 +361,18 @@ class TestUGate:
if expected_behavior == "identity":
# Should measure |0⟩ with high probability
- prob = get_state_probability(results, "0", num_qubits=1)
+ prob = get_state_probability(
+ results, "0", num_qubits=1, backend_name=backend_name
+ )
assert prob > 0.95, (
f"Backend: {backend_name}, "
f"Expected |0⟩ state after U({theta},{phi},{lambd}), got
probability {prob:.4f}"
)
elif expected_behavior == "pauli_x":
# Should measure |1⟩ with high probability
- prob = get_state_probability(results, "1", num_qubits=1)
+ prob = get_state_probability(
+ results, "1", num_qubits=1, backend_name=backend_name
+ )
assert prob > 0.95, (
f"Backend: {backend_name}, "
f"Expected |1⟩ state after U({theta},{phi},{lambd}), got
probability {prob:.4f}"
@@ -517,7 +488,9 @@ class TestPauliZGate:
results = qumat.execute_circuit()
# Calculate probability of expected state
- prob = get_state_probability(results, expected_state, num_qubits=1)
+ prob = get_state_probability(
+ results, expected_state, num_qubits=1, backend_name=backend_name
+ )
assert prob > 0.95, (
f"Backend: {backend_name}, "
@@ -544,7 +517,9 @@ class TestPauliZGate:
results = qumat.execute_circuit()
# Calculate probability of |1⟩ state
- prob = get_state_probability(results, "1", num_qubits=1)
+ prob = get_state_probability(
+ results, "1", num_qubits=1, backend_name=backend_name
+ )
assert prob > 0.95, (
f"Backend: {backend_name}, "
@@ -606,7 +581,9 @@ class TestSingleQubitGatesEdgeCases:
results = qumat.execute_circuit()
# Calculate probability of |0⟩ state
- prob = get_state_probability(results, "0", num_qubits=1)
+ prob = get_state_probability(
+ results, "0", num_qubits=1, backend_name=backend_name
+ )
assert prob > 0.95, (
f"Expected |0⟩ state after gate sequence, got probability {prob}"
@@ -692,7 +669,10 @@ def test_gate_consistency(gate_name,
expected_state_or_behavior):
else:
# For other gates, check specific state probability
prob = get_state_probability(
- results, expected_state_or_behavior, num_qubits=1
+ results,
+ expected_state_or_behavior,
+ num_qubits=1,
+ backend_name=backend_name,
)
results_dict[backend_name] = prob
diff --git a/testing/utils/__init__.py b/testing/utils/__init__.py
index 6c1051bb1..2169a9bd2 100644
--- a/testing/utils/__init__.py
+++ b/testing/utils/__init__.py
@@ -16,6 +16,6 @@
#
from .constants import TESTING_BACKENDS
-from .qumat_helpers import get_backend_config
+from .qumat_helpers import get_backend_config, get_state_probability
-__all__ = ["TESTING_BACKENDS", "get_backend_config"]
+__all__ = ["TESTING_BACKENDS", "get_backend_config", "get_state_probability"]
diff --git a/testing/utils/qumat_helpers.py b/testing/utils/qumat_helpers.py
index ccd1891e2..26337075a 100644
--- a/testing/utils/qumat_helpers.py
+++ b/testing/utils/qumat_helpers.py
@@ -96,3 +96,70 @@ def get_qumat_example_final_state_vector(
state_vector = qumat_instance.get_final_state_vector()
return state_vector
+
+
+def get_state_probability(results, target_state, num_qubits=1,
backend_name=None):
+ """
+ Calculate the probability of measuring a target state.
+
+ Args:
+ results: Dictionary of measurement results from execute_circuit()
+ target_state: Target state as string (e.g., "0", "1", "101") or int
+ num_qubits: Number of qubits in the circuit
+ backend_name: Name of the backend (for handling qubit ordering)
+
+ Returns:
+ Probability of measuring the target state
+ """
+ if isinstance(results, list):
+ results = results[0]
+
+ total_shots = sum(results.values())
+ if total_shots == 0:
+ return 0.0
+
+ # Convert target_state to both string and int formats for comparison
+ if isinstance(target_state, str):
+ target_str = target_state
+ # Convert binary string to integer
+ target_int = int(target_state, 2) if target_state else 0
+ else:
+ target_int = target_state
+ # Convert integer to binary string
+ target_str = format(target_state, f"0{num_qubits}b")
+
+ # Handle backend-specific qubit ordering
+ # Qiskit uses little-endian (rightmost bit is qubit 0)
+ # Amazon Braket and Cirq use big-endian (leftmost bit is qubit 0)
+ if backend_name == "qiskit" and isinstance(target_str, str) and
len(target_str) > 1:
+ # Reverse the string for Qiskit (little-endian)
+ target_str_qiskit = target_str[::-1]
+ else:
+ target_str_qiskit = target_str
+
+ target_count = 0
+ for state, count in results.items():
+ if isinstance(state, str):
+ # For Qiskit, compare with reversed string
+ if backend_name == "qiskit" and len(state) > 1:
+ if state == target_str_qiskit:
+ target_count = count
+ break
+ else:
+ if state == target_str:
+ target_count = count
+ break
+ else:
+ # For Cirq, use integer comparison
+ # Cirq uses big-endian, so the integer representation matches
+ if backend_name == "cirq":
+ if state == target_int:
+ target_count = count
+ break
+ else:
+ # For other backends, also try integer comparison
+ if state == target_int:
+ target_count = count
+ break
+
+ return target_count / total_shots