96 lines
3.6 KiB
Python
96 lines
3.6 KiB
Python
import cupy as cp
|
|
from cuquantum.tensornet import contract
|
|
from cuquantum.tensornet.experimental import contract_decompose
|
|
|
|
|
|
def initial(num_qubits, dtype):
|
|
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.
|
|
|
|
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",
|
|
*mps_tensors[i : i + 2],
|
|
algorithm=kwargs.get("algorithm", None),
|
|
options=kwargs.get("options", None),
|
|
)
|
|
mps_tensors[i : i + 2] = (a, b)
|
|
return mps_tensors
|
|
|
|
|
|
def apply_gate(mps_tensors, gate, qubits, **kwargs):
|
|
"""Apply the gate operand to the MPS tensors in-place.
|
|
|
|
# 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.
|
|
gate: A ndarray-like tensor object representing the gate operand.
|
|
The modes of the gate is expected to be output qubits followed by input qubits, e.g,
|
|
``A, B, a, b`` where ``a, b`` denotes the inputs and ``A, B`` denotes the outputs.
|
|
qubits: A sequence of integers denoting the qubits that the gate is applied onto.
|
|
algorithm: The contract and decompose algorithm to use for gate application.
|
|
Can be either a `dict` or a `ContractDecomposeAlgorithm`.
|
|
options: Specify the contract and decompose options.
|
|
|
|
Returns:
|
|
The updated MPS tensors.
|
|
"""
|
|
|
|
n_qubits = len(qubits)
|
|
if n_qubits == 1:
|
|
# single-qubit gate
|
|
i = qubits[0]
|
|
mps_tensors[i] = contract(
|
|
"ipj,qp->iqj", mps_tensors[i], gate, options=kwargs.get("options", None)
|
|
) # in-place update
|
|
elif n_qubits == 2:
|
|
# two-qubit gate
|
|
i, j = qubits
|
|
if i > j:
|
|
# swap qubits order
|
|
return apply_gate(mps_tensors, gate.transpose(1, 0, 3, 2), (j, i), **kwargs)
|
|
elif i + 1 == j:
|
|
# two adjacent qubits
|
|
a, _, b = contract_decompose(
|
|
"ipj,jqk,rspq->irj,jsk",
|
|
*mps_tensors[i : i + 2],
|
|
gate,
|
|
algorithm=kwargs.get("algorithm", None),
|
|
options=kwargs.get("options", None),
|
|
)
|
|
mps_tensors[i : i + 2] = (a, b) # in-place update
|
|
else:
|
|
# non-adjacent two-qubit gate
|
|
# step 1: swap i with i+1
|
|
mps_site_right_swap(mps_tensors, i, **kwargs)
|
|
# step 2: apply gate to (i+1, j) pair. This amounts to a recursive swap until the two qubits are adjacent
|
|
apply_gate(mps_tensors, gate, (i + 1, j), **kwargs)
|
|
# step 3: swap back i and i+1
|
|
mps_site_right_swap(mps_tensors, i, **kwargs)
|
|
else:
|
|
raise NotImplementedError("Only one- and two-qubit gates supported")
|