92 lines
3.2 KiB
Python
92 lines
3.2 KiB
Python
import cupy as cp
|
|
import numpy as np
|
|
|
|
|
|
class QiboCircuitToEinsum:
|
|
def __init__(self, circuit, dtype="complex128"):
|
|
self.backend = cp
|
|
self.dtype = getattr(self.backend, dtype)
|
|
|
|
self.input_tensor_counter = np.zeros((circuit.nqubits,))
|
|
self.gate_tensors = []
|
|
for gate in circuit.queue:
|
|
for target in gate.target_qubits:
|
|
self.input_tensor_counter[target] += 1
|
|
for control in gate.control_qubits:
|
|
self.input_tensor_counter[control] += 1
|
|
gate_qubits = gate.control_qubits + gate.target_qubits
|
|
# self.gate_tensors is to extract into a list the gate matrix together with the qubit id that it is acting on
|
|
self.gate_tensors.append(
|
|
(
|
|
cp.asarray(gate.matrix).reshape((2,) * 2 * len(gate_qubits)),
|
|
gate_qubits,
|
|
)
|
|
)
|
|
# self.active_qubits is to identify qubits with at least 1 gate acting on it in the whole circuit.
|
|
self.active_qubits = [
|
|
indx for indx, value in enumerate(self.input_tensor_counter) if value > 0
|
|
]
|
|
|
|
def state_vector(self):
|
|
input_tensor_count = len(self.active_qubits)
|
|
|
|
input_operands = self._get_bitstring_tensors(
|
|
"0" * input_tensor_count, self.dtype, backend=self.backend
|
|
)
|
|
|
|
(
|
|
mode_labels,
|
|
qubits_frontier,
|
|
next_frontier,
|
|
) = self._init_mode_labels_from_qubits(self.active_qubits)
|
|
|
|
gate_mode_labels, gate_operands = self._parse_gates_to_mode_labels_operands(
|
|
self.gate_tensors, qubits_frontier, next_frontier
|
|
)
|
|
|
|
operands = input_operands + gate_operands
|
|
mode_labels += gate_mode_labels
|
|
|
|
out_list = []
|
|
for key in qubits_frontier:
|
|
out_list.append(qubits_frontier[key])
|
|
|
|
operand_exp_interleave = [x for y in zip(operands, mode_labels) for x in y]
|
|
operand_exp_interleave.append(out_list)
|
|
return operand_exp_interleave
|
|
|
|
def _init_mode_labels_from_qubits(self, qubits):
|
|
frontier_dict = {}
|
|
n = len(qubits)
|
|
for x in range(n):
|
|
frontier_dict[qubits[x]] = x
|
|
return [[i] for i in range(n)], frontier_dict, n
|
|
|
|
def _get_bitstring_tensors(self, bitstring, dtype=np.complex128, backend=cp):
|
|
asarray = backend.asarray
|
|
state_0 = asarray([1, 0], dtype=dtype)
|
|
state_1 = asarray([0, 1], dtype=dtype)
|
|
|
|
basis_map = {"0": state_0, "1": state_1}
|
|
|
|
operands = [basis_map[ibit] for ibit in bitstring]
|
|
return operands
|
|
|
|
def _parse_gates_to_mode_labels_operands(
|
|
self, gates, qubits_frontier, next_frontier
|
|
):
|
|
mode_labels = []
|
|
operands = []
|
|
|
|
for tensor, gate_qubits in gates:
|
|
operands.append(tensor)
|
|
input_mode_labels = []
|
|
output_mode_labels = []
|
|
for q in gate_qubits:
|
|
input_mode_labels.append(qubits_frontier[q])
|
|
output_mode_labels.append(next_frontier)
|
|
qubits_frontier[q] = next_frontier
|
|
next_frontier += 1
|
|
mode_labels.append(output_mode_labels + input_mode_labels)
|
|
return mode_labels, operands
|