diff --git a/doc/source/getting-started/quickstart.rst b/doc/source/getting-started/quickstart.rst index 7c58a03c..4e9c1a59 100644 --- a/doc/source/getting-started/quickstart.rst +++ b/doc/source/getting-started/quickstart.rst @@ -1,29 +1,30 @@ Quick start =========== +In this section, we provide an example of two qubit ciruit simulation using qibotn package in Qibo simulator. First, the backend is to be set with appropriate run card settings, followed by the circuit simulation using Qibo documentation. + Setting the backend """"""""""""""""""" -QiboTN supports two backends cutensornet (using CuQuantum library) and Quimbbackend (using Quimb library) for tensor network based simulations. The backend can be set using the following command line. -For CuQuantum library, +QiboTN offers two backends: cutensornet (using cuQuantum library) and qutensornet (using Quimb library) for tensor network based simulations. At present, cutensornet backend works only for GPUs whereas qutensornet for CPUs. The backend can be set using the following command line. + +To use cuQuantum library, cutensornet can be specified as follows:: -.. testcode:: - qibo.set_backend(backend="qibotn", platform="cutensornet", runcard=computation_settings) -.. + qibo.set_backend( + backend="qibotn", platform="cutensornet", runcard=computation_settings + ) -and for Quimb library +Similarly, to use Quimb library, qutensornet can be set as follows:: -.. testcode:: qibo.set_backend( - backend="qibotn", platform="QuimbBackend", runcard=computation_settings + backend="qibotn", platform="qutensornet", runcard=computation_settings ) -.. Setting the runcard """"""""""""""""""" -Basic structure of runcard is -.. testcode:: +The basic structure of the runcard is as follows:: + computation_settings = { "MPI_enabled": False, "MPS_enabled": False, @@ -32,21 +33,44 @@ Basic structure of runcard is "pauli_string_pattern": "IXZ", }, } -.. + + +**MPI_enabled:** Setting this option *True* results in parallel execution of circuit using MPI (Message Passing Interface). At present, only works for cutensornet platform. + +**MPS_enabled:** This option is set *True* for Matrix Product State (MPS) based calculations where as general tensor network structure is used for *False* value. + +**NCCL_enabled:** This is set *True* for cutensoret interface for further acceleration while using Nvidia Collective Communication Library (NCCL). + +**expectation_enabled:** This option is set *True* while calculating expecation value of the circuit. Observable whose expectation value is to be calculated is passed as a string in the dict format as {"pauli_string_pattern": "observable"}. When the option is set *False*, the dense vector state of the circuit is calculated. + Basic example """"""""""""" -.. testcode:: - # Construct the circuit +The following is a basic example to execute a two qubit circuit and print the final state in dense vector form using quimb backend:: + + # Set the quimb backend + qibo.set_backend( + backend="qibotn", platform="qutensornet", runcard=computation_settings + ) + + # Set the runcard + computation_settings = { + "MPI_enabled": False, + "MPS_enabled": False, + "NCCL_enabled": False, + "expectation_enabled": False, + } + + # Construct the circuit with two qubits c = Circuit(2) - # Add some gates + + # Apply Hadamard gates on first and second qubit c.add(gates.H(0)) c.add(gates.H(1)) # Execute the circuit and obtain the final state result = c() + # Print the final state print(result.state()) - -.. diff --git a/doc/source/index.rst b/doc/source/index.rst index 79ff8d52..e30d36dc 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -25,8 +25,8 @@ The supported HPC configurations are: Currently, the supported tensor network libraries are: -- [cuQuantum](https://github.com/NVIDIA/cuQuantum), an NVIDIA SDK of optimized libraries and tools for accelerating quantum computing workflows. -- [quimb](https://quimb.readthedocs.io/en/latest/), an easy but fast python library for ‘quantum information many-body’ calculations, focusing primarily on tensor networks. +- `cuQuantum `_, an NVIDIA SDK of optimized libraries and tools for accelerating quantum computing workflows. +- `quimb `_, an easy but fast python library for ‘quantum information many-body’ calculations, focusing primarily on tensor networks. How to Use the Documentation ============================ @@ -50,10 +50,10 @@ Contents getting-started/index .. toctree:: - :maxdepth: 2 + :maxdepth: 1 :caption: Main documentation - api-reference/modules + api-reference/qibotn Developer guides .. toctree:: @@ -64,6 +64,9 @@ Contents Qibolab docs Qibocal docs Qibosoq docs + Qibochem docs + Qibotn docs + Qibo-cloud-backends docs Indices and tables ================== diff --git a/src/qibotn/backends/cutensornet.py b/src/qibotn/backends/cutensornet.py index fc010e97..c8341bde 100644 --- a/src/qibotn/backends/cutensornet.py +++ b/src/qibotn/backends/cutensornet.py @@ -18,6 +18,7 @@ class CuTensorNet(NumpyBackend): # pragma: no cover # CI does not test for GPU + """Creates CuQuantum backend for QiboTN.""" def __init__(self, runcard): super().__init__() @@ -92,6 +93,14 @@ def set_precision(self, precision): super().set_precision(precision) def cuda_type(self, dtype="complex64"): + """Get CUDA Type. + + Parameters: + dtype (str, optional): Either single ("complex64") or double (complex128) precision. Defaults to "complex64". + + Returns: + CUDA Type: tuple of cuquantum.cudaDataType and cuquantum.ComputeType + """ if dtype in CUDA_TYPES: return CUDA_TYPES[dtype] else: @@ -100,15 +109,15 @@ def cuda_type(self, dtype="complex64"): def execute_circuit( self, circuit, initial_state=None, nshots=None, return_array=False ): # pragma: no cover - """Executes a quantum circuit. + """Executes a quantum circuit using selected TN backend. - Args: + Parameters: circuit (:class:`qibo.models.circuit.Circuit`): Circuit to execute. initial_state (:class:`qibo.models.circuit.Circuit`): Circuit to prepare the initial state. If ``None`` the default ``|00...0>`` state is used. Returns: - xxx. + QuantumState or numpy.ndarray: If `return_array` is False, returns a QuantumState object representing the quantum state. If `return_array` is True, returns a numpy array representing the quantum state. """ import qibotn.eval as eval diff --git a/src/qibotn/backends/quimb.py b/src/qibotn/backends/quimb.py index 0a34eefe..1c861202 100644 --- a/src/qibotn/backends/quimb.py +++ b/src/qibotn/backends/quimb.py @@ -59,7 +59,7 @@ def execute_circuit( If ``None`` the default ``|00...0>`` state is used. Returns: - xxx. + QuantumState or numpy.ndarray: If `return_array` is False, returns a QuantumState object representing the quantum state. If `return_array` is True, returns a numpy array representing the quantum state. """ import qibotn.eval_qu as eval diff --git a/src/qibotn/circuit_convertor.py b/src/qibotn/circuit_convertor.py index 14af79de..03e96fa7 100644 --- a/src/qibotn/circuit_convertor.py +++ b/src/qibotn/circuit_convertor.py @@ -26,6 +26,12 @@ def __init__(self, circuit, dtype="complex128"): self.circuit = circuit def state_vector_operands(self): + """Create the operands for dense vector computation in the interleave + format. + + Returns: + Operands for the contraction in the interleave format. + """ input_bitstring = "0" * len(self.active_qubits) input_operands = self._get_bitstring_tensors(input_bitstring) @@ -79,11 +85,25 @@ def _parse_gates_to_mode_labels_operands( return mode_labels, operands def op_shape_from_qubits(self, nqubits): - """Modify tensor to cuQuantum shape (qubit_states,input_output) * - qubits_involved.""" + """Modify tensor to cuQuantum shape. + + Parameters: + nqubits (int): The number of qubits in quantum circuit. + + Returns: + (qubit_states,input_output) * nqubits + """ return (2, 2) * nqubits def init_intermediate_circuit(self, circuit): + """Initialize the intermediate circuit representation. + + This method initializes the intermediate circuit representation by extracting gate matrices and qubit IDs + from the given quantum circuit. + + Parameters: + circuit (object): The quantum circuit object. + """ self.gate_tensors = [] gates_qubits = [] @@ -105,6 +125,15 @@ def init_intermediate_circuit(self, circuit): self.active_qubits = np.unique(gates_qubits) def init_basis_map(self, backend, dtype): + """Initialize the basis map for the quantum circuit. + + This method initializes a basis map for the quantum circuit, which maps binary + strings representing qubit states to their corresponding quantum state vectors. + + Parameters: + backend (object): The backend object providing the array conversion method. + dtype (object): The data type for the quantum state vectors. + """ asarray = backend.asarray state_0 = asarray([1, 0], dtype=dtype) state_1 = asarray([0, 1], dtype=dtype) @@ -112,6 +141,14 @@ def init_basis_map(self, backend, dtype): self.basis_map = {"0": state_0, "1": state_1} def init_inverse_circuit(self, circuit): + """Initialize the inverse circuit representation. + + This method initializes the inverse circuit representation by extracting gate matrices and qubit IDs + from the given quantum circuit. + + Parameters: + circuit (object): The quantum circuit object. + """ self.gate_tensors_inverse = [] gates_qubits_inverse = [] @@ -135,7 +172,7 @@ def init_inverse_circuit(self, circuit): def get_pauli_gates(self, pauli_map, dtype="complex128", backend=cp): """Populate the gates for all pauli operators. - Args: + Parameters: pauli_map: A dictionary mapping qubits to pauli operators. dtype: Data type for the tensor operands. backend: The package the tensor operands belong to. @@ -159,6 +196,15 @@ def get_pauli_gates(self, pauli_map, dtype="complex128", backend=cp): return gates def expectation_operands(self, pauli_string): + """Create the operands for pauli string expectation computation in the + interleave format. + + Parameters: + pauli_string: A string representating the list of pauli gates. + + Returns: + Operands for the contraction in the interleave format. + """ input_bitstring = "0" * self.circuit.nqubits input_operands = self._get_bitstring_tensors(input_bitstring) diff --git a/src/qibotn/circuit_to_mps.py b/src/qibotn/circuit_to_mps.py index af8acd5a..9d98c194 100644 --- a/src/qibotn/circuit_to_mps.py +++ b/src/qibotn/circuit_to_mps.py @@ -7,6 +7,15 @@ class QiboCircuitToMPS: + """A helper class to convert Qibo circuit to MPS. + + Parameters: + circ_qibo: The quantum circuit object. + gate_algo(dict): Dictionary for SVD and QR settings. + datatype (str): Either single ("complex64") or double (complex128) precision. + rand_seed(int): Seed for random number generator. + """ + def __init__( self, circ_qibo, diff --git a/src/qibotn/eval.py b/src/qibotn/eval.py index 6375aa6a..245aa5ea 100644 --- a/src/qibotn/eval.py +++ b/src/qibotn/eval.py @@ -9,14 +9,31 @@ def dense_vector_tn(qibo_circ, datatype): """Convert qibo circuit to tensornet (TN) format and perform contraction to - dense vector.""" + dense vector. + + Parameters: + qibo_circ: The quantum circuit object. + datatype (str): Either single ("complex64") or double (complex128) precision. + + Returns: + Dense vector of quantum circuit. + """ myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) return contract(*myconvertor.state_vector_operands()) def expectation_pauli_tn(qibo_circ, datatype, pauli_string_pattern): """Convert qibo circuit to tensornet (TN) format and perform contraction to - expectation of given Pauli string.""" + expectation of given Pauli string. + + Parameters: + qibo_circ: The quantum circuit object. + datatype (str): Either single ("complex64") or double (complex128) precision. + pauli_string_pattern(str): pauli string pattern. + + Returns: + Expectation of quantum circuit due to pauli string. + """ myconvertor = QiboCircuitToEinsum(qibo_circ, dtype=datatype) return contract( *myconvertor.expectation_operands( @@ -35,6 +52,14 @@ def dense_vector_tn_MPI(qibo_circ, datatype, n_samples=8): the least costly contraction path. This is sped up with multi thread. After pathfinding the optimal path is used in the actual contraction to give a dense vector representation of the TN. + + Parameters: + qibo_circ: The quantum circuit object. + datatype (str): Either single ("complex64") or double (complex128) precision. + n_samples(int): Number of samples for pathfinding. + + Returns: + Dense vector of quantum circuit. """ from cuquantum import Network @@ -102,6 +127,14 @@ def dense_vector_tn_nccl(qibo_circ, datatype, n_samples=8): the least costly contraction path. This is sped up with multi thread. After pathfinding the optimal path is used in the actual contraction to give a dense vector representation of the TN. + + Parameters: + qibo_circ: The quantum circuit object. + datatype (str): Either single ("complex64") or double (complex128) precision. + n_samples(int): Number of samples for pathfinding. + + Returns: + Dense vector of quantum circuit. """ from cupy.cuda import nccl from cuquantum import Network @@ -183,6 +216,15 @@ def expectation_pauli_tn_nccl(qibo_circ, datatype, pauli_string_pattern, n_sampl select the least costly contraction path. This is sped up with multi thread. After pathfinding the optimal path is used in the actual contraction to give an expectation value. + + Parameters: + qibo_circ: The quantum circuit object. + datatype (str): Either single ("complex64") or double (complex128) precision. + pauli_string_pattern(str): pauli string pattern. + n_samples(int): Number of samples for pathfinding. + + Returns: + Expectation of quantum circuit due to pauli string. """ from cupy.cuda import nccl from cuquantum import Network @@ -266,6 +308,15 @@ def expectation_pauli_tn_MPI(qibo_circ, datatype, pauli_string_pattern, n_sample select the least costly contraction path. This is sped up with multi thread. After pathfinding the optimal path is used in the actual contraction to give an expectation value. + + Parameters: + qibo_circ: The quantum circuit object. + datatype (str): Either single ("complex64") or double (complex128) precision. + pauli_string_pattern(str): pauli string pattern. + n_samples(int): Number of samples for pathfinding. + + Returns: + Expectation of quantum circuit due to pauli string. """ from cuquantum import Network from mpi4py import MPI # this line initializes MPI @@ -326,7 +377,16 @@ def expectation_pauli_tn_MPI(qibo_circ, datatype, pauli_string_pattern, n_sample def dense_vector_mps(qibo_circ, gate_algo, datatype): """Convert qibo circuit to matrix product state (MPS) format and perform - contraction to dense vector.""" + contraction to dense vector. + + Parameters: + qibo_circ: The quantum circuit object. + gate_algo(dict): Dictionary for SVD and QR settings. + datatype (str): Either single ("complex64") or double (complex128) precision. + + Returns: + Dense vector of quantum circuit. + """ myconvertor = QiboCircuitToMPS(qibo_circ, gate_algo, dtype=datatype) mps_helper = MPSContractionHelper(myconvertor.num_qubits) @@ -339,6 +399,13 @@ def pauli_string_gen(nqubits, pauli_string_pattern): """Used internally to generate the string based on given pattern and number of qubit. + Parameters: + nqubits(int): Number of qubits of Quantum Circuit + pauli_string_pattern(str): Strings representing sequence of pauli gates. + + Returns: + String representation of the actual pauli string from the pattern. + Example: pattern: "XZ", number of qubit: 7, output = XZXZXZX """ if nqubits <= 0: diff --git a/src/qibotn/eval_qu.py b/src/qibotn/eval_qu.py index d6064e95..6cb9f395 100644 --- a/src/qibotn/eval_qu.py +++ b/src/qibotn/eval_qu.py @@ -3,7 +3,15 @@ def init_state_tn(nqubits, init_state_sv): - """Create a matrix product state directly from a dense vector.""" + """Create a matrix product state directly from a dense vector. + + Args: + nqubits (int): Total number of qubits in the circuit. + init_state_sv (list): Initial state in the dense vector form. + + Returns: + list: Matrix product state representation of the dense vector. + """ dims = tuple(2 * np.ones(nqubits, dtype=int)) @@ -11,9 +19,16 @@ def init_state_tn(nqubits, init_state_sv): def dense_vector_tn_qu(qasm: str, initial_state, mps_opts, backend="numpy"): - """Evaluate QASM with Quimb. + """Evaluate circuit in QASM format with Quimb. + + Args: + qasm (str): QASM program. + initial_state (list): Initial state in the dense vector form. If ``None`` the default ``|00...0>`` state is used. + mps_opts (dict): Parameters to tune the gate_opts for mps settings in ``class quimb.tensor.circuit.CircuitMPS``. + backend (str): Backend to perform the contraction with, e.g. ``numpy``, ``cupy``, ``jax``. Passed to ``opt_einsum``. - backend (quimb): numpy, cupy, jax. Passed to ``opt_einsum``. + Returns: + list: Amplitudes of final state after the simulation of the circuit. """ if initial_state is not None: diff --git a/src/qibotn/mps_contraction_helper.py b/src/qibotn/mps_contraction_helper.py index e4d827a4..c4370044 100644 --- a/src/qibotn/mps_contraction_helper.py +++ b/src/qibotn/mps_contraction_helper.py @@ -7,30 +7,17 @@ class MPSContractionHelper: """A helper class to compute various quantities for a given MPS. Interleaved format is used to construct the input args for `cuquantum.contract`. - A concrete example on how the modes are populated for a 7-site MPS is provided - below:: - 0 2 4 6 8 10 12 14 - bra -----A-----B-----C-----D-----E-----F-----G----- - | | | | | | | - 1| 3| 5| 7| 9| 11| 13| - | | | | | | | - ket -----a-----b-----c-----d-----e-----f-----g----- - 15 16 17 18 19 20 21 22 + Reference: https://github.com/NVIDIA/cuQuantum/blob/main/python/samples/cutensornet/tn_algorithms/mps_algorithms.ipynb - - The follwing compute quantities are supported: + The following compute quantities are supported: - the norm of the MPS. - the equivalent state vector from the MPS. - the expectation value for a given operator. - the equivalent state vector after multiplying an MPO to an MPS. - Note that for the nth MPS tensor (rank-3), the modes of the tensor are expected to be `(i,p,j)` - where i denotes the bonding mode with the (n-1)th tensor, p denotes the physical mode for the qubit and - j denotes the bonding mode with the (n+1)th tensor. - - Args: + Parameters: num_qubits: The number of qubits for the MPS. """ @@ -46,7 +33,7 @@ def contract_norm(self, mps_tensors, options=None): """Contract the corresponding tensor network to form the norm of the MPS. - Args: + Parameters: mps_tensors: A list of rank-3 ndarray-like tensor objects. The indices of the ith tensor are expected to be bonding index to the i-1 tensor, the physical mode, and then the bonding index to the i+1th tensor. @@ -67,7 +54,7 @@ def contract_state_vector(self, mps_tensors, options=None): """Contract the corresponding tensor network to form the state vector representation of the MPS. - Args: + Parameters: mps_tensors: A list of rank-3 ndarray-like tensor objects. The indices of the ith tensor are expected to be bonding index to the i-1 tensor, the physical mode, and then the bonding index to the i+1th tensor. @@ -89,7 +76,7 @@ def contract_expectation( """Contract the corresponding tensor network to form the expectation of the MPS. - Args: + Parameters: mps_tensors: A list of rank-3 ndarray-like tensor objects. The indices of the ith tensor are expected to be bonding index to the i-1 tensor, the physical mode, and then the bonding index to the i+1th tensor. diff --git a/src/qibotn/mps_utils.py b/src/qibotn/mps_utils.py index f0fa811c..878a5aaf 100644 --- a/src/qibotn/mps_utils.py +++ b/src/qibotn/mps_utils.py @@ -2,18 +2,32 @@ from cuquantum import contract from cuquantum.cutensornet.experimental import contract_decompose -# Reference: https://github.com/NVIDIA/cuQuantum/blob/main/python/samples/cutensornet/tn_algorithms/mps_algorithms.ipynb - def initial(num_qubits, dtype): - r"""Generate the MPS with an initial state of :math:`\ket{00...00}`""" + r"""Generate the MPS with an initial state of :math:`\ket{00...00}` + + Parameters: + num_qubits: Number of qubits in the Quantum Circuit. + dtype: Either single ("complex64") or double (complex128) precision. + + Returns: + The initial MPS tensors. + """ state_tensor = cp.asarray([1, 0], dtype=dtype).reshape(1, 2, 1) mps_tensors = [state_tensor] * num_qubits return mps_tensors def mps_site_right_swap(mps_tensors, i, **kwargs): - """Perform the swap operation between the ith and i+1th MPS tensors.""" + """Perform the swap operation between the ith and i+1th MPS tensors. + + Parameters: + mps_tensors: Tensors representing MPS + i (int): index of the tensor to swap + + Returns: + The updated MPS tensors. + """ # contraction followed by QR decomposition a, _, b = contract_decompose( "ipj,jqk->iqj,jpk", @@ -28,7 +42,9 @@ def mps_site_right_swap(mps_tensors, i, **kwargs): def apply_gate(mps_tensors, gate, qubits, **kwargs): """Apply the gate operand to the MPS tensors in-place. - Args: + # Reference: https://github.com/NVIDIA/cuQuantum/blob/main/python/samples/cutensornet/tn_algorithms/mps_algorithms.ipynb + + Parameters: mps_tensors: A list of rank-3 ndarray-like tensor objects. The indices of the ith tensor are expected to be the bonding index to the i-1 tensor, the physical mode, and then the bonding index to the i+1th tensor.