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

commit 58cf4952a03e15e012bad13a4e36ea93d2df9559
Author: KUAN-HAO HUANG <[email protected]>
AuthorDate: Sun Dec 7 09:33:17 2025 +0800

    [QDP] Add fidelity test (#694)
    
    * add a fidelity test
    
    * fix format
    
    * reformat
    
    * effectively zero
---
 qdp/qdp-python/tests/test_high_fidelity.py | 239 +++++++++++++++++++++++++++++
 1 file changed, 239 insertions(+)

diff --git a/qdp/qdp-python/tests/test_high_fidelity.py 
b/qdp/qdp-python/tests/test_high_fidelity.py
new file mode 100644
index 000000000..05bc41987
--- /dev/null
+++ b/qdp/qdp-python/tests/test_high_fidelity.py
@@ -0,0 +1,239 @@
+#
+# 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.
+
+"""
+Tests include: full-stack verification, async pipeline, fidelity metrics,
+zero-copy validation, and edge cases (boundaries, stability, memory, threads).
+"""
+
+import pytest
+import torch
+import numpy as np
+import concurrent.futures
+from mahout_qdp import QdpEngine
+
+np.random.seed(2026)
+
+# ASYNC_THRESHOLD = 1MB / sizeof(f64) = 131072
+PIPELINE_CHUNK_SIZE = 131072
+
+
+def calculate_fidelity(
+    state_vector_gpu: torch.Tensor, ground_truth_cpu: np.ndarray
+) -> float:
+    """Calculate quantum state fidelity: F = |<ψ_gpu | ψ_cpu>|²"""
+    psi_gpu = state_vector_gpu.cpu().numpy()
+
+    if np.any(np.isnan(psi_gpu)) or np.any(np.isinf(psi_gpu)):
+        return 0.0
+
+    assert psi_gpu.shape == ground_truth_cpu.shape, (
+        f"Shape mismatch: {psi_gpu.shape} vs {ground_truth_cpu.shape}"
+    )
+
+    overlap = np.vdot(ground_truth_cpu, psi_gpu)
+    fidelity = np.abs(overlap) ** 2
+    return float(fidelity)
+
+
[email protected](scope="module")
+def engine():
+    """Initialize QDP engine (module-scoped singleton)."""
+    try:
+        return QdpEngine(0)
+    except RuntimeError as e:
+        pytest.skip(f"CUDA initialization failed: {e}")
+
+
+# 1. Core Logic and Boundary Tests
+
+
[email protected]
[email protected](
+    "num_qubits, data_size, desc",
+    [
+        (4, 16, "Small - Sync Path"),
+        (10, 1000, "Medium - Padding Logic"),
+        (18, PIPELINE_CHUNK_SIZE, "Boundary - Exact Chunk Size"),
+        (18, PIPELINE_CHUNK_SIZE + 1, "Boundary - Chunk + 1"),
+        (18, PIPELINE_CHUNK_SIZE * 2, "Boundary - Two Exact Chunks"),
+        (20, 1_000_000, "Large - Async Pipeline"),
+    ],
+)
+def test_amplitude_encoding_fidelity_comprehensive(engine, num_qubits, 
data_size, desc):
+    """Test fidelity across sync path, async pipeline, and chunk boundaries."""
+    print(f"\n[Test Case] {desc} (Size: {data_size})")
+
+    raw_data = np.random.rand(data_size).astype(np.float64)
+    norm = np.linalg.norm(raw_data)
+    expected_state = raw_data / norm
+
+    state_len = 1 << num_qubits
+    if data_size < state_len:
+        padding = np.zeros(state_len - data_size, dtype=np.float64)
+        expected_state = np.concatenate([expected_state, padding])
+
+    expected_state_complex = expected_state.astype(np.complex128)
+    qtensor = engine.encode(raw_data.tolist(), num_qubits, "amplitude")
+    torch_state = torch.from_dlpack(qtensor)
+
+    assert torch_state.is_cuda, "Tensor must be on GPU"
+    assert torch_state.dtype == torch.complex128, "Tensor must be Complex128"
+    assert torch_state.shape[0] == state_len, "Tensor shape must match 2^n"
+
+    fidelity = calculate_fidelity(torch_state, expected_state_complex)
+    print(f"Fidelity: {fidelity:.16f}")
+
+    assert fidelity > (1.0 - 1e-14), f"Fidelity loss in {desc}! F={fidelity}"
+
+
[email protected]
+def test_complex_integrity(engine):
+    """Verify imaginary part is effectively zero for amplitude encoding."""
+    num_qubits = 12
+    data_size = 3000  # Non-power-of-2 size
+
+    raw_data = np.random.rand(data_size).astype(np.float64)
+    qtensor = engine.encode(raw_data.tolist(), num_qubits, "amplitude")
+    torch_state = torch.from_dlpack(qtensor)
+
+    imag_error = torch.sum(torch.abs(torch_state.imag)).item()
+    print(f"\nSum of imaginary parts (should be near 0): {imag_error}")
+
+    # Use tolerance check (< 1e-16) instead of strict equality to handle 
floating-point noise
+    assert imag_error < 1e-16, (
+        f"State vector contains significant imaginary components! 
({imag_error})"
+    )
+
+
+# 2. Numerical Stability Tests
+
+
[email protected]
+def test_numerical_stability_underflow(engine):
+    """Test precision with extremely small values (1e-150)."""
+    num_qubits = 4
+    data = [1e-150] * 16
+
+    qtensor = engine.encode(data, num_qubits, "amplitude")
+    torch_state = torch.from_dlpack(qtensor)
+
+    assert not torch.isnan(torch_state).any(), "Result contains NaN for small 
inputs"
+
+    probs = torch.abs(torch_state) ** 2
+    total_prob = torch.sum(probs).item()
+    assert abs(total_prob - 1.0) < 1e-10, f"Normalization failed: {total_prob}"
+
+
+# 3. Memory Leak Tests
+
+
[email protected]
+def test_memory_leak_quantitative(engine):
+    """Quantitative memory leak test using torch.cuda.memory_allocated()."""
+    num_qubits = 10
+    data = [0.1] * 1024
+    iterations = 500
+
+    _ = torch.from_dlpack(engine.encode(data, num_qubits, "amplitude"))
+    torch.cuda.synchronize()
+
+    start_mem = torch.cuda.memory_allocated()
+    print(f"\nStart GPU Memory: {start_mem} bytes")
+
+    for _ in range(iterations):
+        qtensor = engine.encode(data, num_qubits, "amplitude")
+        t = torch.from_dlpack(qtensor)
+        del t
+        del qtensor
+
+    torch.cuda.synchronize()
+    end_mem = torch.cuda.memory_allocated()
+    print(f"End GPU Memory:   {end_mem} bytes")
+
+    assert end_mem == start_mem, (
+        f"Memory leak detected! Leaked {end_mem - start_mem} bytes"
+    )
+
+
[email protected]
+def test_memory_safety_stress(engine):
+    """Stress test: rapid encode/release to verify DLPack deleter."""
+    import gc
+
+    num_qubits = 10
+    data = [0.1] * 1024
+    iterations = 1000
+
+    print(f"\nStarting memory stress test ({iterations} iterations)...")
+
+    for _ in range(iterations):
+        qtensor = engine.encode(data, num_qubits, "amplitude")
+        t = torch.from_dlpack(qtensor)
+        del t
+        del qtensor
+
+    gc.collect()
+    torch.cuda.empty_cache()
+    print("Memory stress test passed (no crash).")
+
+
+# 4. Thread Safety Tests
+
+
[email protected]
+def test_multithreaded_access(engine):
+    """Test concurrent access from multiple threads (validates Send+Sync)."""
+
+    def worker_task(thread_id):
+        size = 100 + thread_id
+        data = np.random.rand(size).tolist()
+        try:
+            qtensor = engine.encode(data, 10, "amplitude")
+            t = torch.from_dlpack(qtensor)
+            return t.is_cuda
+        except Exception as e:
+            return e
+
+    num_threads = 8
+    print(f"\nStarting concurrent stress test with {num_threads} threads...")
+
+    with concurrent.futures.ThreadPoolExecutor(max_workers=num_threads) as 
executor:
+        futures = [executor.submit(worker_task, i) for i in range(num_threads)]
+
+        for future in concurrent.futures.as_completed(futures):
+            result = future.result()
+            if isinstance(result, Exception):
+                pytest.fail(f"Thread failed with error: {result}")
+            assert result is True, "Thread execution result invalid"
+
+    print("Multithreaded access check passed.")
+
+
+# 5. Error Propagation Tests
+
+
[email protected]
+def test_error_propagation(engine):
+    """Verify Rust errors are correctly propagated to Python RuntimeError."""
+    with pytest.raises(RuntimeError, match="Input data cannot be 
empty|empty|Empty"):
+        engine.encode([], 5, "amplitude")
+
+    with pytest.raises(RuntimeError, match="at least 1|qubit|Qubit"):
+        engine.encode([1.0], 0, "amplitude")
+
+    with pytest.raises(RuntimeError, match="exceeds state vector 
size|exceed|capacity"):
+        engine.encode([1.0, 1.0, 1.0], 1, "amplitude")

Reply via email to