def collect_heavy_outputs(wfn_sim: NumpyWavefunctionSimulator, permutations: np.ndarray, gates: np.ndarray) -> List[int]: """ Collects and returns those 'heavy' bitstrings which are output with greater than median probability among all possible bitstrings on the given qubits. The method uses the provided wfn_sim to calculate the probability of measuring each bitstring from the output of the circuit comprised of the given permutations and gates. :param wfn_sim: a NumpyWavefunctionSimulator that can simulate the provided program :param permutations: array of depth-many arrays of size n_qubits indicating a qubit permutation :param gates: depth by num_gates_per_layer many matrix representations of 2q gates. The first row of matrices is the earliest-time layer of 2q gates applied. :return: a list of the heavy outputs of the circuit, represented as ints """ wfn_sim.reset() for layer_idx, (perm, layer) in enumerate(zip(permutations, gates)): for gate_idx, gate in enumerate(layer): wfn_sim.do_gate_matrix(gate, (perm[gate_idx], perm[gate_idx + 1])) # Note that probabilities are ordered lexicographically with qubit 0 leftmost. probabilities = np.abs(wfn_sim.wf.reshape(-1))**2 median_prob = median(probabilities) # store the integer indices, which implicitly represent the bitstring outcome. heavy_outputs = [ idx for idx, prob in enumerate(probabilities) if prob > median_prob ] return heavy_outputs
def single_q_tomo_fixture(): qubits = [0] qc = get_test_qc(n_qubits=len(qubits)) # Generate random unitary u_rand = haar_rand_unitary(2 ** 1, rs=np.random.RandomState(52)) state_prep = Program().defgate("RandUnitary", u_rand) state_prep.inst([("RandUnitary", qubits[0])]) # True state wfn = NumpyWavefunctionSimulator(n_qubits=1) psi = wfn.do_gate_matrix(u_rand, qubits=[0]).wf.reshape(-1) rho_true = np.outer(psi, psi.T.conj()) # Get data from QVM tomo_expt = generate_state_tomography_experiment(state_prep, qubits) results = list(measure_observables(qc=qc, tomo_experiment=tomo_expt, n_shots=4000)) return results, rho_true