Customized quantum gate¶
The numqi.sim submodule supports customized quantum gate, even the not-unitary "gate"
import numpy as np
try:
import numqi
except ImportError:
%pip install numqi
import numqi
Let's define a measurement "gate"
class MeasureGate:
def __init__(self, index, seed=None, name='measure'):
self.kind = 'custom'
self.name = name
self.requires_grad = False
index = numqi.utils.hf_tuple_of_int(index)
assert all(x==y for x,y in zip(sorted(index),index)), 'index must be sorted'
self.index = index
self.np_rng = numqi.random.get_numpy_rng(seed)
self.bitstr = None
self.probability = None
def forward(self, q0):
self.bitstr,self.probability,q1 = numqi.sim.state.measure_quantum_vector(q0, self.index, self.np_rng)
return q1
The function numqi.sim.state.measure_quantum_vector is used to measure a quantum vector on some of qubits. After the measurement, thoes qubits will be collapsed to the computational basis state self.bitstr according to the probability distribution of the measurement result self.probablity. The final quantum state will become q1.
Actually, numqi.sim has builtin measurement operation, let's name this newer one as measure_custom().
circ = numqi.sim.Circuit()
circ.register_custom_gate('measure_custom', MeasureGate)
circ.H(0)
circ.cnot(0, 1)
gate_measure = circ.measure_custom(index=(0,1))
Above, we make a Bell state and measure it.
q0 = numqi.sim.state.new_base(num_qubit=2)
q1 = circ.apply_state(q0)
print(f'{gate_measure.bitstr=}')
print(f'{gate_measure.probability=}')
print(f'{q1=}')
gate_measure.bitstr=[1, 1] gate_measure.probability=array([0.5, 0. , 0. , 0.5]) q1=array([0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j])
As expected, only 00 and 11 are possible. Let's make a GHZ state.
circ = numqi.sim.Circuit()
circ.register_custom_gate('measure_custom', MeasureGate)
circ.H(0)
circ.cnot(0, 1)
circ.cnot(1, 2)
gate_measure = circ.measure_custom(index=(0,1,2))
q0 = numqi.sim.state.new_base(num_qubit=3)
q1 = circ.apply_state(q0)
print(f'{gate_measure.bitstr=}')
print(f'{gate_measure.probability=}')
print(f'{q1=}')
gate_measure.bitstr=[1, 1, 1] gate_measure.probability=array([0.5, 0. , 0. , 0. , 0. , 0. , 0. , 0.5]) q1=array([0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j])
One can also measure many times, and one can insert quantum circuits between these measurements.
num_qubit = 5
num_layer = 3
measure_gate_list = []
circ = numqi.sim.Circuit()
circ.register_custom_gate('measure_custom', MeasureGate)
np_rng = np.random.default_rng()
for _ in range(num_layer):
for ind0 in range(num_qubit):
circ.u3(ind0, args=np_rng.uniform(0, 2*np.pi, size=3))
tmp0 = list(range(0, num_qubit-1, 2)) + list(range(1, num_qubit-1, 2))
for ind0 in tmp0:
circ.cnot(ind0, ind0+1)
measure_gate_list.append(circ.measure_custom(index=(0,1)))
q0 = numqi.sim.state.new_base(num_qubit)
q1 = circ.apply_state(q0)
print(np.linalg.norm(q1)) #1
print('probability:', measure_gate_list[0].probability)
for ind0,gate_i in enumerate(measure_gate_list):
print(f'[gate-{ind0}] bitstr:', gate_i.bitstr)
1.0 probability: [2.68804750e-01 7.03602687e-06 1.91385206e-05 7.31169075e-01] [gate-0] bitstr: [1, 1] [gate-1] bitstr: [1, 1] [gate-2] bitstr: [1, 0]