Source code for spheres.spin_circuits.qiskit

"""
Qiskit circuits for preparing spin-j states as permutation symmetric multiqubit states.
"""

from ..stars.pure import *
from ..symmetrization import *
from copy import deepcopy

from qiskit import QuantumCircuit, execute, ClassicalRegister, QuantumRegister
from qiskit import Aer, IBMQ, transpile
from qiskit.providers.ibmq.managed import IBMQJobManager
from qiskit.quantum_info.operators import Operator
from qiskit.ignis.verification.tomography import state_tomography_circuits, StateTomographyFitter

[docs]def Rk_qiskit(k): """ Single qubit operator employed in symmetrization circuit to prepare control qubits. Parameters ---------- k : int Returns ------- O : qiskit.quantum_info.operators.Operator """ return Operator((1/np.sqrt(k+1))*\ np.array([[1, -np.sqrt(k)],\ [np.sqrt(k), 1]]))
[docs]def Tkj_qiskit(k, j): """ Two qubit operator employed in symmetrization circuit to prepare control qubits. Parameters ---------- k : int j : int Returns ------- O : qiskit.quantum_info.operators.Operator """ return Operator((1/np.sqrt(k-j+1))*\ np.array([[np.sqrt(k-j+1), 0, 0, 0],\ [0, 1, np.sqrt(k-j), 0],\ [0, -np.sqrt(k-j), 1, 0],\ [0, 0, 0, np.sqrt(k-j+1)]]))
[docs]def spin_sym_qiskit(spin): """ Given a spin-j state, constructs a qiskit circuit which prepares that state as a permutation symmetric state of 2j qubits. The circuit is probabalistic and depends on the control qubits being postselected on the up/0 state. Returns a dictionary whose elements are: - "circuit": Qiskit circuit - "spin_qubits": Qiskit quantum register for the qubits encoding the spin - "cntrl_qubits": Qiskit quantum register for the control qubits - "cntrl_bits": Qiskit classical register for the control measurements - "postselect_on": "cntrl_bits" (which classical register to control on) - "postselection": "0" x len(cntrl_bits) (postselection state to impose) Parameters ---------- spin : qt.Qobj Spin-j state. Returns ------- circuit_info : dict """ j = (spin.shape[0]-1)/2 n = int(2*j) r = int(n*(n-1)/2) stars = spin_sph(spin) circ = QuantumCircuit() spin_qubits = QuantumRegister(len(stars), "spin_qubits") circ.add_register(spin_qubits) for i, star in enumerate(stars): theta, phi = star circ.ry(theta, spin_qubits[i]) circ.rz(phi, spin_qubits[i]) cntrl_qubits = QuantumRegister(r, "cntrl_qubits") circ.add_register(cntrl_qubits) qubits = list(cntrl_qubits) + list(spin_qubits) offset = r for k in range(1, n): offset = offset-k circ.append(Rk_qiskit(k), [qubits[offset]]) for i in range(k-1): circ.append(Tkj_qiskit(k, i+1), [qubits[offset+i+1], qubits[offset+i]]) for i in range(k-1, -1, -1): circ.fredkin(qubits[offset+i], qubits[r+k], qubits[r+i]) for i in range(k-2, -1, -1): circ.append(Tkj_qiskit(k, i+1).adjoint(), [qubits[offset+i+1], qubits[offset+i]]) circ.append(Rk_qiskit(k).adjoint(), [qubits[offset]]) cntrl_bits = ClassicalRegister(r, "cntrl_bits") circ.add_register(cntrl_bits) for i in range(r): circ.measure(cntrl_qubits[i], cntrl_bits[i]) return {"circuit": circ,\ "spin_qubits": spin_qubits,\ "cntrl_qubits": cntrl_qubits,\ "cntrl_bits": cntrl_bits,\ "postselect_on": "cntrl_bits",\ "postselection": "0"*r}
[docs]def postselect_results_qiskit(circ_info, raw_results): """ Performs postselection on Qiskit results given information in the provided dictionary. Removes classical registers corresponding to circ_info["postselect_on"], leaving only those results with satisfy circ_info["postselection"]. Parameters ---------- circ_info : dict raw_results : qiskit.Result Returns ------- postselected_results : qiskit.Result """ n_not_cntrl = [len(creg) for creg in circ_info["circuit"].cregs if creg.name == circ_info["postselect_on"]][0] new_result = deepcopy(raw_results) for resultidx, _ in enumerate(raw_results.results): old_counts, new_counts = raw_results.get_counts(resultidx), {} postselection_index = next(i for i, v in enumerate(new_result.results[resultidx].header.creg_sizes[::-1]) if v[0] == circ_info["postselect_on"]) new_result.results[resultidx].header.clbit_labels = list(filter(lambda clbit_label: clbit_label[0] != circ_info["postselect_on"], new_result.results[resultidx].header.clbit_labels)) new_result.results[resultidx].header.creg_sizes = list(filter(lambda creg_size: creg_size[0] != circ_info["postselect_on"], new_result.results[resultidx].header.creg_sizes)) new_result.results[resultidx].header.memory_slots = circ_info["circuit"].num_qubits - n_not_cntrl for reg_key in old_counts: reg_bits = reg_key.split(" ") if reg_bits[postselection_index] == circ_info["postselection"]: new_counts[" ".join([reg_bit for i, reg_bit in enumerate(reg_bits) if i != postselection_index])] = old_counts[reg_key] new_result.results[resultidx].data.counts = new_counts return new_result
[docs]def spin_tomography_qiskit(circ_info, backend_name="qasm_simulator", shots=8000): """ Performs tomography on the provided symmetric multiqubit circuit, given postselection on the control qubits. Parameters ---------- circ_info : dict backend_name : str Qiskit backend. shots : int Number of shots. Returns ------- dm : qt.Qobj Reconstructed spin-j density matrix. """ circ_sans_aux = circ_info["circuit"].remove_final_measurements(inplace=False) tomog_circs = state_tomography_circuits(circ_info["circuit"], circ_info["spin_qubits"]) tomog_circs_sans_aux = state_tomography_circuits(circ_sans_aux, circ_info["spin_qubits"]) if backend_name == "qasm_simulator": backend = Aer.get_backend("qasm_simulator") job = execute(tomog_circs, backend, shots=shots) raw_results = job.result() else: provider = IBMQ.load_account() job_manager = IBMQJobManager() backend = provider.get_backend(backend_name) job = job_manager.run(transpile(tomog_circs, backend=backend), backend=backend, name="spin_sym", shots=shots) raw_results = job.results().combine_results() postselected_results = postselect_results_qiskit(circ_info, raw_results) tomog_fit = StateTomographyFitter(postselected_results, tomog_circs_sans_aux) dm = qt.Qobj(tomog_fit.fit()) dm.dims = [[2]*len(circ_info["spin_qubits"]), [2]*len(circ_info["spin_qubits"])] return sym_spin(dm)