[ 
https://issues.apache.org/jira/browse/MAHOUT-900?focusedWorklogId=1002889&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-1002889
 ]

ASF GitHub Bot logged work on MAHOUT-900:
-----------------------------------------

                Author: ASF GitHub Bot
            Created on: 01/Feb/26 16:16
            Start Date: 01/Feb/26 16:16
    Worklog Time Spent: 10m 
      Work Description: ryankert01 commented on code in PR #938:
URL: https://github.com/apache/mahout/pull/938#discussion_r2751509333


##########
testing/qdp/test_bindings.py:
##########
@@ -1080,3 +1024,190 @@ def test_iqp_encode_errors():
     # Non-finite parameter (negative infinity)
     with pytest.raises(RuntimeError, match="must be finite"):
         engine.encode([float("-inf"), 0.0], 2, "iqp-z")
+
+
+# ==================== IQP FWT Optimization Tests ====================
+
+
[email protected]
+def test_iqp_fwt_normalization():
+    """Test that FWT-optimized IQP produces normalized states (requires 
GPU)."""
+    pytest.importorskip("torch")
+    import torch
+    from _qdp import QdpEngine
+
+    if not torch.cuda.is_available():
+        pytest.skip("GPU required for QdpEngine")
+
+    engine = QdpEngine(0)
+
+    # Test across FWT threshold (FWT_MIN_QUBITS = 4)
+    for num_qubits in [3, 4, 5, 6, 7, 8]:
+        # Full IQP (n + n*(n-1)/2 parameters)
+        data_len = num_qubits + num_qubits * (num_qubits - 1) // 2
+        data = [0.1 * i for i in range(data_len)]
+
+        qtensor = engine.encode(data, num_qubits, "iqp")
+        torch_tensor = torch.from_dlpack(qtensor)
+
+        # Verify normalization (sum of |amplitude|^2 = 1)
+        norm = torch.sum(torch.abs(torch_tensor) ** 2)
+        assert torch.isclose(norm, torch.tensor(1.0, device="cuda:0"), 
atol=1e-6), (
+            f"IQP {num_qubits} qubits not normalized: got {norm.item()}"
+        )
+
+
[email protected]
+def test_iqp_z_fwt_normalization():
+    """Test that FWT-optimized IQP-Z produces normalized states (requires 
GPU)."""
+    pytest.importorskip("torch")
+    import torch
+    from _qdp import QdpEngine
+
+    if not torch.cuda.is_available():
+        pytest.skip("GPU required for QdpEngine")
+
+    engine = QdpEngine(0)
+
+    # Test across FWT threshold
+    for num_qubits in [3, 4, 5, 6, 7, 8]:
+        data = [0.2 * i for i in range(num_qubits)]
+
+        qtensor = engine.encode(data, num_qubits, "iqp-z")
+        torch_tensor = torch.from_dlpack(qtensor)
+
+        norm = torch.sum(torch.abs(torch_tensor) ** 2)
+        assert torch.isclose(norm, torch.tensor(1.0, device="cuda:0"), 
atol=1e-6), (
+            f"IQP-Z {num_qubits} qubits not normalized: got {norm.item()}"
+        )
+
+
[email protected]
+def test_iqp_fwt_zero_params_gives_zero_state():
+    """Test that zero parameters produce |0...0⟩ state (requires GPU).
+
+    With zero parameters, the IQP circuit is H^n * I * H^n = I,
+    so |0⟩^n maps to |0⟩^n with amplitude 1 at index 0.
+    """
+    pytest.importorskip("torch")
+    import torch
+    from _qdp import QdpEngine
+
+    if not torch.cuda.is_available():
+        pytest.skip("GPU required for QdpEngine")
+
+    engine = QdpEngine(0)
+
+    # Test FWT-optimized path (n >= 4)
+    for num_qubits in [4, 5, 6]:
+        data_len = num_qubits + num_qubits * (num_qubits - 1) // 2
+        data = [0.0] * data_len
+
+        qtensor = engine.encode(data, num_qubits, "iqp")
+        torch_tensor = torch.from_dlpack(qtensor)
+
+        # Should get |0...0⟩: amplitude 1 at index 0, 0 elsewhere
+        state_len = 1 << num_qubits
+        expected = torch.zeros((1, state_len), dtype=torch_tensor.dtype, 
device="cuda:0")
+        expected[0, 0] = 1.0 + 0j
+
+        assert torch.allclose(torch_tensor, expected, atol=1e-6), (
+            f"IQP {num_qubits} qubits with zero params should give |0⟩ state"
+        )
+
+
[email protected]
+def test_iqp_fwt_batch_normalization():
+    """Test that FWT-optimized batch IQP produces normalized states (requires 
GPU)."""
+    pytest.importorskip("torch")
+    import torch
+    from _qdp import QdpEngine
+
+    if not torch.cuda.is_available():
+        pytest.skip("GPU required for QdpEngine")
+
+    engine = QdpEngine(0)
+
+    # Test batch encoding across FWT threshold
+    for num_qubits in [4, 5, 6]:
+        data_len = num_qubits + num_qubits * (num_qubits - 1) // 2
+        batch_size = 8
+
+        data = torch.tensor(
+            [[0.1 * (i + j * data_len) for i in range(data_len)] for j in 
range(batch_size)],
+            dtype=torch.float64
+        )
+
+        qtensor = engine.encode(data, num_qubits, "iqp")
+        torch_tensor = torch.from_dlpack(qtensor)
+
+        assert torch_tensor.shape == (batch_size, 1 << num_qubits)
+
+        # Check each sample is normalized
+        for i in range(batch_size):
+            norm = torch.sum(torch.abs(torch_tensor[i]) ** 2)
+            assert torch.isclose(norm, torch.tensor(1.0, device="cuda:0"), 
atol=1e-6), (
+                f"IQP batch sample {i} not normalized: got {norm.item()}"
+            )
+
+
[email protected]
+def test_iqp_fwt_deterministic():
+    """Test that FWT-optimized IQP is deterministic (requires GPU)."""
+    pytest.importorskip("torch")
+    import torch
+    from _qdp import QdpEngine
+
+    if not torch.cuda.is_available():
+        pytest.skip("GPU required for QdpEngine")
+
+    engine = QdpEngine(0)
+
+    num_qubits = 6  # Uses FWT path
+    data_len = num_qubits + num_qubits * (num_qubits - 1) // 2
+    data = [0.3 * i for i in range(data_len)]
+
+    # Run encoding twice
+    qtensor1 = engine.encode(data, num_qubits, "iqp")
+    tensor1 = torch.from_dlpack(qtensor1).clone()
+
+    qtensor2 = engine.encode(data, num_qubits, "iqp")
+    tensor2 = torch.from_dlpack(qtensor2)
+
+    # Results should be identical
+    assert torch.allclose(tensor1, tensor2, atol=1e-10), (
+        "IQP FWT encoding should be deterministic"
+    )
+
+
[email protected]
+def test_iqp_fwt_shared_vs_global_memory_threshold():
+    """Test IQP encoding at shared memory threshold boundary (requires GPU).
+
+    FWT_SHARED_MEM_THRESHOLD = 10, so:
+    - n <= 10: uses shared memory FWT
+    - n > 10: uses global memory FWT
+    """
+    pytest.importorskip("torch")
+    import torch
+    from _qdp import QdpEngine
+
+    if not torch.cuda.is_available():
+        pytest.skip("GPU required for QdpEngine")
+
+    engine = QdpEngine(0)
+
+    # Test at and around the shared memory threshold
+    for num_qubits in [9, 10]:

Review Comment:
   Nice catch, updated!





Issue Time Tracking
-------------------

    Worklog Id:     (was: 1002889)
    Time Spent: 1h 40m  (was: 1.5h)

> RandomSeedGenerator samples / output k texts incorrectly
> --------------------------------------------------------
>
>                 Key: MAHOUT-900
>                 URL: https://issues.apache.org/jira/browse/MAHOUT-900
>             Project: Mahout
>          Issue Type: Bug
>          Components: classic
>    Affects Versions: 0.5
>            Reporter: Sean R. Owen
>            Assignee: Sean R. Owen
>            Priority: Minor
>             Fix For: 0.6
>
>         Attachments: MAHOUT-900.patch
>
>          Time Spent: 1h 40m
>  Remaining Estimate: 0h
>
> {code}
>           int currentSize = chosenTexts.size();
>           if (currentSize < k) {
>             chosenTexts.add(newText);
>             chosenClusters.add(newCluster);
>           } else if (random.nextInt(currentSize + 1) == 0) { // with chance 
> 1/(currentSize+1) pick new element
>             int indexToRemove = random.nextInt(currentSize); // evict one 
> chosen randomly
>             chosenTexts.remove(indexToRemove);
>             chosenClusters.remove(indexToRemove);
>             chosenTexts.add(newText);
>             chosenClusters.add(newCluster);
>           }
> {code}
> The second "if" condition ought to be "!= 0", right? Only if it is 0 do we 
> skip the body, which removes an existing element, since the new element 
> itself is evicted.
> Second, this code:
> {code}
>         for (int i = 0; i < k; i++) {
>           writer.append(chosenTexts.get(i), chosenClusters.get(i));
>         }
> {code}
> ... assumes that at least k elements existed in the input, and fails 
> otherwise. Probably need to cap this.
> Patch attached.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to