Repository: PECOS-packages/PECOS (language: python)

Issue:
Adding a numpy-based basic statevector simulator for more exhaustive testing
- Adding a numpy-based basic statevector simulator.
- Adding more exhaustive tests comparing simulators against the numpy basic simulator.
Update: Update cuquantum elements to support 24.0
Update for the latest cuquantum library 0.24.0 interface.  Tested on A100.
Adding a numpy-based basic statevector simulator for more exhaustive testing
- Adding a numpy-based basic statevector simulator.
- Adding more exhaustive tests comparing simulators against the numpy basic simulator.
Update: Update cuquantum elements to support 24.0
Update for the latest cuquantum library 0.24.0 interface.  Tested on A100.


Discussion / hints:
Please update the copyright dates of the files newly introduced to 2024.

Please update the copyright dates of the files newly introduced to 2024.


Fix patch:
diff --git a/pyproject.toml b/pyproject.toml
index 30cf393e..bfd78312 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -59,7 +59,8 @@ simulators = [
     "projectq>=0.5.0,<0.9.0",
 ]
 cuda = [
-    "cuquantum-python>=23.6.0",
+    "cuquantum-python>=24.03.0",
+    "custatevec>=1.6.0",
     "cupy>=10.4.0",
 ]
 wasmtime = [
diff --git a/python/pecos/simulators/custatevec/bindings.py b/python/pecos/simulators/custatevec/bindings.py
index 7e929416..bdc8d605 100644
--- a/python/pecos/simulators/custatevec/bindings.py
+++ b/python/pecos/simulators/custatevec/bindings.py
@@ -11,7 +11,7 @@
 
 import pecos.simulators.custatevec.gates_one_qubit as one_q
 import pecos.simulators.custatevec.gates_two_qubit as two_q
-from pecos.simulators.custatevec.gates_init import init_zero
+from pecos.simulators.custatevec.gates_init import init_one, init_zero
 from pecos.simulators.custatevec.gates_meas import meas_z
 
 # Supporting gates from table:
@@ -19,7 +19,15 @@
 
 gate_dict = {
     "Init": init_zero,
+    "init |0>": init_zero,
+    "init |1>": init_one,
     "Measure": meas_z,
+    "measure Z": meas_z,
+    "leak": init_zero,
+    "leak |0>": init_zero,
+    "leak |1>": init_one,
+    "unleak |0>": init_zero,
+    "unleak |1>": init_one,
     "I": one_q.identity,
     "X": one_q.X,
     "Y": one_q.Y,
@@ -28,12 +36,19 @@
     "RY": one_q.RY,
     "RZ": one_q.RZ,
     "R1XY": one_q.R1XY,
+    "RXY1Q": one_q.R1XY,
     "SX": one_q.SX,
     "SXdg": one_q.SXdg,
+    "SqrtX": one_q.SX,
+    "SqrtXd": one_q.SXdg,
     "SY": one_q.SY,
     "SYdg": one_q.SYdg,
+    "SqrtY": one_q.SY,
+    "SqrtYd": one_q.SYdg,
     "SZ": one_q.SZ,
     "SZdg": one_q.SZdg,
+    "SqrtZ": one_q.SZ,
+    "SqrtZd": one_q.SZdg,
     "H": one_q.H,
     "F": one_q.F,
     "Fdg": one_q.Fdg,
@@ -51,6 +66,7 @@
     "SYY": two_q.SYY,
     "SYYdg": two_q.SYYdg,
     "SZZ": two_q.SZZ,
+    "SqrtZZ": two_q.SZZ,
     "SZZdg": two_q.SZZdg,
     "SWAP": two_q.SWAP,
 }
diff --git a/python/pecos/simulators/custatevec/gates_init.py b/python/pecos/simulators/custatevec/gates_init.py
index b7c73049..0942ca29 100644
--- a/python/pecos/simulators/custatevec/gates_init.py
+++ b/python/pecos/simulators/custatevec/gates_init.py
@@ -26,3 +26,16 @@ def init_zero(state, qubit: int, **params: Any) -> None:
 
     if result:
         X(state, qubit)
+
+
+def init_one(state, qubit: int, **params: Any) -> None:
+    """Initialise or reset the qubit to state |1>
+
+    Args:
+        state: An instance of CuStateVec
+        qubit: The index of the qubit to be initialised
+    """
+    result = meas_z(state, qubit)
+
+    if not result:
+        X(state, qubit)
diff --git a/python/pecos/simulators/custatevec/gates_meas.py b/python/pecos/simulators/custatevec/gates_meas.py
index 1fe5d914..5fa93db8 100644
--- a/python/pecos/simulators/custatevec/gates_meas.py
+++ b/python/pecos/simulators/custatevec/gates_meas.py
@@ -36,12 +36,12 @@ def meas_z(state, qubit: int, **params: Any) -> int:
 
     result = cusv.measure_on_z_basis(
         handle=state.libhandle,
-        sv=state.vector.data.ptr,
+        sv=state.cupy_vector.data.ptr,
         sv_data_type=state.cuda_type,
         n_index_bits=state.num_qubits,  # Number of qubits in the statevector
         basis_bits=[target],  # The index of the qubit being measured
         n_basis_bits=1,  # Number of qubits being measured
-        rand_num=np.random.random(),  # Source of randomness for the measurement
+        randnum=np.random.random(),  # Source of randomness for the measurement
         collapse=cusv.Collapse.NORMALIZE_AND_ZERO,  # Collapse and normalise
     )
     state.stream.synchronize()
diff --git a/python/pecos/simulators/custatevec/gates_one_qubit.py b/python/pecos/simulators/custatevec/gates_one_qubit.py
index f2a62ba2..326d2918 100644
--- a/python/pecos/simulators/custatevec/gates_one_qubit.py
+++ b/python/pecos/simulators/custatevec/gates_one_qubit.py
@@ -34,7 +34,7 @@ def _apply_one_qubit_matrix(state, qubit: int, matrix: cp.ndarray) -> None:
 
     cusv.apply_matrix(
         handle=state.libhandle,
-        sv=state.vector.data.ptr,
+        sv=state.cupy_vector.data.ptr,
         sv_data_type=state.cuda_type,
         n_index_bits=state.num_qubits,
         matrix=matrix.data.ptr,
@@ -47,8 +47,8 @@ def _apply_one_qubit_matrix(state, qubit: int, matrix: cp.ndarray) -> None:
         control_bit_values=[],  # No value of control bit assigned
         n_controls=0,
         compute_type=state.compute_type,
-        workspace=0,  # Let cuQuantum use the mempool we configured
-        workspace_size=0,  # Let cuQuantum use the mempool we configured
+        extra_workspace=0,  # Let cuQuantum use the mempool we configured
+        extra_workspace_size_in_bytes=0,  # Let cuQuantum use the mempool we configured
     )
     state.stream.synchronize()
 
diff --git a/python/pecos/simulators/custatevec/gates_two_qubit.py b/python/pecos/simulators/custatevec/gates_two_qubit.py
index bc185c41..70b74784 100644
--- a/python/pecos/simulators/custatevec/gates_two_qubit.py
+++ b/python/pecos/simulators/custatevec/gates_two_qubit.py
@@ -39,7 +39,7 @@ def _apply_controlled_matrix(state, control: int, target: int, matrix: cp.ndarra
 
     cusv.apply_matrix(
         handle=state.libhandle,
-        sv=state.vector.data.ptr,
+        sv=state.cupy_vector.data.ptr,
         sv_data_type=state.cuda_type,
         n_index_bits=state.num_qubits,
         matrix=matrix.data.ptr,
@@ -52,8 +52,8 @@ def _apply_controlled_matrix(state, control: int, target: int, matrix: cp.ndarra
         control_bit_values=[],  # No value of control bit assigned
         n_controls=1,
         compute_type=state.compute_type,
-        workspace=0,  # Let cuQuantum use the mempool we configured
-        workspace_size=0,  # Let cuQuantum use the mempool we configured
+        extra_workspace=0,  # Let cuQuantum use the mempool we configured
+        extra_workspace_size_in_bytes=0,  # Let cuQuantum use the mempool we configured
     )
     state.stream.synchronize()
 
@@ -142,7 +142,7 @@ def _apply_two_qubit_matrix(state, qubits: tuple[int, int], matrix: cp.ndarray)
 
     cusv.apply_matrix(
         handle=state.libhandle,
-        sv=state.vector.data.ptr,
+        sv=state.cupy_vector.data.ptr,
         sv_data_type=state.cuda_type,
         n_index_bits=state.num_qubits,
         matrix=matrix.data.ptr,
@@ -155,8 +155,8 @@ def _apply_two_qubit_matrix(state, qubits: tuple[int, int], matrix: cp.ndarray)
         control_bit_values=[],  # No value of control bit assigned
         n_controls=0,
         compute_type=state.compute_type,
-        workspace=0,  # Let cuQuantum use the mempool we configured
-        workspace_size=0,  # Let cuQuantum use the mempool we configured
+        extra_workspace=0,  # Let cuQuantum use the mempool we configured
+        extra_workspace_size_in_bytes=0,  # Let cuQuantum use the mempool we configured
     )
     state.stream.synchronize()
 
@@ -218,7 +218,7 @@ def RYY(state, qubits: tuple[int, int], angles: tuple[float], **params: Any) ->
             math.cos(theta / 2),
             0,
             0,
-            math.sin(theta / 2),
+            1j * math.sin(theta / 2),
             0,
             math.cos(theta / 2),
             -1j * math.sin(theta / 2),
@@ -227,7 +227,7 @@ def RYY(state, qubits: tuple[int, int], angles: tuple[float], **params: Any) ->
             -1j * math.sin(theta / 2),
             math.cos(theta / 2),
             0,
-            math.sin(theta / 2),
+            1j * math.sin(theta / 2),
             0,
             0,
             math.cos(theta / 2),
@@ -380,7 +380,7 @@ def SWAP(state, qubits: tuple[int, int], **params: Any) -> None:
     # Possibly faster since it may just be an internal qubit relabelling or sv reshape
     cusv.apply_generalized_permutation_matrix(
         handle=state.libhandle,
-        sv=state.vector.data.ptr,
+        sv=state.cupy_vector.data.ptr,
         sv_data_type=state.cuda_type,
         n_index_bits=state.num_qubits,
         permutation=[0, 2, 1, 3],  # Leave |00> and |11> where they are, swap the other two
@@ -392,7 +392,7 @@ def SWAP(state, qubits: tuple[int, int], **params: Any) -> None:
         controls=[],
         control_bit_values=[],  # No value of control bit assigned
         n_controls=0,
-        workspace=0,  # Let cuQuantum use the mempool we configured
-        workspace_size=0,  # Let cuQuantum use the mempool we configured
+        extra_workspace=0,  # Let cuQuantum use the mempool we configured
+        extra_workspace_size_in_bytes=0,  # Let cuQuantum use the mempool we configured
     )
     state.stream.synchronize()
diff --git a/python/pecos/simulators/custatevec/state.py b/python/pecos/simulators/custatevec/state.py
index 997b3b04..90e3b2f4 100644
--- a/python/pecos/simulators/custatevec/state.py
+++ b/python/pecos/simulators/custatevec/state.py
@@ -12,6 +12,7 @@
 import random
 
 import cupy as cp
+import numpy as np
 from cuquantum import ComputeType, cudaDataType
 from cuquantum import custatevec as cusv
 
@@ -52,8 +53,8 @@ def __init__(self, num_qubits, seed=None) -> None:
         self.compute_type = ComputeType.COMPUTE_64F
 
         # Allocate the statevector in GPU and initialize it to |0>
-        self.vector = cp.zeros(shape=2**num_qubits, dtype=self.cp_type)
-        self.vector[0] = 1
+        self.cupy_vector = cp.zeros(shape=2**num_qubits, dtype=self.cp_type)
+        self.cupy_vector[0] = 1
 
         ####################################################
         # Set up cuStateVec library and GPU memory handles #
@@ -97,7 +98,18 @@ def free(ptr, size, stream):
         mem_handler = (malloc, free, "GPU memory handler")
         cusv.set_device_mem_handler(self.libhandle, mem_handler)
 
+    def reset(self):
+        """Reset the quantum state for another run without reinitializing."""
+        # Initialize all qubits in the zero state
+        self.vector = cp.zeros(shape=2**self.num_qubits, dtype=self.cp_type)
+        self.vector[0] = 1
+        return self
+
     def __del__(self) -> None:
-        # CuPy will release GPU memory when the variable ``self.vector`` is no longer
+        # CuPy will release GPU memory when the variable ``self.cupy_vector`` is no longer
         # reachable. However, we need to manually destroy the library handle.
         cusv.destroy(self.libhandle)
+
+    @property
+    def vector(self) -> np.ndarray:
+        return self.cupy_vector.get()


Test patch:
diff --git a/tests/integration/state_sim_tests/test_statevec.py b/tests/integration/state_sim_tests/test_statevec.py
index 48727bf8..ba63c462 100644
--- a/tests/integration/state_sim_tests/test_statevec.py
+++ b/tests/integration/state_sim_tests/test_statevec.py
@@ -21,7 +21,7 @@
 try:
     import cuquantum
 
-    imported_cuquantum = vparse(cuquantum._version.__version__) >= vparse("23.6.0")  # noqa: SLF001
+    imported_cuquantum = vparse(cuquantum._version.__version__) >= vparse("24.03.0")  # noqa: SLF001
     import cupy as cp
 
     imported_cupy = vparse(version("cupy")) >= vparse("10.4.0")