diff --git a/src/qibotn/QiboCircuitConvertor.py b/src/qibotn/QiboCircuitConvertor.py index dcbf3e7..2ed3ce7 100644 --- a/src/qibotn/QiboCircuitConvertor.py +++ b/src/qibotn/QiboCircuitConvertor.py @@ -3,31 +3,50 @@ import numpy as np class QiboCircuitToEinsum: + """This class takes a quantum circuit defined in Qibo (i.e. a Circuit object) + and convert it to an equivalent Tensor Network (TN) representation that is formatted for + cuQuantum' contract method to compute the state vectors. + See document for detail: https://docs.nvidia.com/cuda/cuquantum/python/api/generated/cuquantum.contract.html + + When the class is constructed, it first process the circuit to an intermediate form by extracting the gate matrix + and grouping each gate with its corresponding qubit it is acting on to a list. + + It is then converted it to an equivalent TN expression following the Einstein + summation convention through the class function state_vector_operand(). + The output is to be used by cuQuantum's contract() for computation of the state vectors of the circuit. + """ + def __init__(self, circuit, dtype="complex128"): + def op_shape_from_qubits(nqubits): + """This function is to modify the shape of the tensor to the required format by cuQuantum + (qubit_states,) * input_output * qubits_involved + """ + return (2,) * 2 * nqubits + self.backend = cp self.dtype = getattr(self.backend, dtype) - self.input_tensor_counter = np.zeros((circuit.nqubits,)) self.gate_tensors = [] + gates_qubits = [] + 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 + gates_qubits.extend(gate_qubits) + # self.gate_tensors is to extract into a list the gate matrix together with the qubit id that it is acting on + # https://github.com/NVIDIA/cuQuantum/blob/6b6339358f859ea930907b79854b90b2db71ab92/python/cuquantum/cutensornet/_internal/circuit_parser_utils_cirq.py#L32 + required_shape = op_shape_from_qubits(len(gate_qubits)) self.gate_tensors.append( ( - cp.asarray(gate.matrix).reshape((2,) * 2 * len(gate_qubits)), + cp.asarray(gate.matrix).reshape(required_shape), 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): + # self.active_qubits is to identify qubits with at least 1 gate acting on it in the whole circuit. + self.active_qubits = np.unique(gates_qubits) + + def state_vector_operands(self): input_tensor_count = len(self.active_qubits) input_operands = self._get_bitstring_tensors(