def test_for_negative_probabilities(): # trivial program to do state tomography on prog = Program(I(0)) # make TomographyExperiment expt_settings = [ExperimentSetting(zeros_state([0]), pt) for pt in [sI(0), sX(0), sY(0), sZ(0)]] experiment_1q = TomographyExperiment(settings=expt_settings, program=prog) # make a quantum computer object device = NxDevice(nx.complete_graph(1)) qc_density = QuantumComputer( name="testy!", qam=PyQVM(n_qubits=1, quantum_simulator_type=ReferenceDensitySimulator), device=device, compiler=DummyCompiler(), ) # initialize with a pure state initial_density = np.array([[1.0, 0.0], [0.0, 0.0]]) qc_density.qam.wf_simulator.density = initial_density try: list(measure_observables(qc=qc_density, tomo_experiment=experiment_1q, n_shots=3000)) except ValueError as e: # the error is from np.random.choice by way of self.rs.choice in ReferenceDensitySimulator assert str(e) != "probabilities are not non-negative" # initialize with a mixed state initial_density = np.array([[0.9, 0.0], [0.0, 0.1]]) qc_density.qam.wf_simulator.density = initial_density try: list(measure_observables(qc=qc_density, tomo_experiment=experiment_1q, n_shots=3000)) except ValueError as e: assert str(e) != "probabilities are not non-negative"
def test_measure_observables_many_progs(forest): expts = [ ExperimentSetting(sI(), o1 * o2) for o1, o2 in itertools.product([sI(0), sX(0), sY(0), sZ(0)], [sI(1), sX(1), sY(1), sZ(1)]) ] qc = get_qc('2q-qvm') qc.qam.random_seed = 51 for prog in _random_2q_programs(): suite = TomographyExperiment(expts, program=prog, qubits=[0, 1]) assert len(suite) == 4 * 4 gsuite = group_experiments(suite) assert len( gsuite ) == 3 * 3 # can get all the terms with I for free in this case wfn = WavefunctionSimulator() wfn_exps = {} for expt in expts: wfn_exps[expt] = wfn.expectation(gsuite.program, PauliSum([expt.out_operator])) for res in measure_observables(qc, gsuite, n_shots=1_000): np.testing.assert_allclose(wfn_exps[res.setting], res.expectation, atol=0.1)
def test_no_complex_coeffs(forest): qc = get_qc('2q-qvm') suite = TomographyExperiment([ExperimentSetting(sI(), 1.j * sY(0))], program=Program(X(0)), qubits=[0]) with pytest.raises(ValueError): res = list(measure_observables(qc, suite))
def test_identity(forest): qc = get_qc('2q-qvm') suite = TomographyExperiment([ExperimentSetting(sI(), 0.123 * sI(0))], program=Program(X(0)), qubits=[0]) result = list(measure_observables(qc, suite))[0] assert result.expectation == 0.123
def test_variance_bootstrap(): qubits = [0, 1] qc = get_test_qc(n_qubits=len(qubits)) state_prep = Program([H(q) for q in qubits]) state_prep.inst(CZ(qubits[0], qubits[1])) tomo_expt = generate_state_tomography_experiment(state_prep, qubits) results = list( measure_observables(qc=qc, tomo_experiment=tomo_expt, n_shots=10_000)) estimate, status = iterative_mle_state_estimate(results=results, qubits=qubits, dilution=0.5) rho_est = estimate.estimate.state_point_est purity = np.trace(rho_est @ rho_est) purity = np.real_if_close(purity) assert purity.imag == 0.0 def my_mle_estimator(_r, _q): return iterative_mle_state_estimate(results=_r, qubits=_q, dilution=0.5, entropy_penalty=0.0, beta=0.0)[0] boot_purity, boot_var = estimate_variance(results=results, qubits=qubits, tomo_estimator=my_mle_estimator, functional=dm.purity, n_resamples=5, project_to_physical=False) np.testing.assert_allclose(purity, boot_purity, atol=2 * np.sqrt(boot_var), rtol=0.01)
def measurement_func(request, test_qc): if request.param == 'wfn': return lambda expt: list(wfn_measure_observables(n_qubits=2, tomo_expt=expt)) elif request.param == 'sampling': return lambda expt: list(measure_observables(qc=test_qc, tomo_experiment=expt, n_shots=4000)) else: raise ValueError()
def run(qc: QuantumComputer, seq: List[Program], subgraph: List[List[int]], num_trials: int) -> np.ndarray: prog = merge_programs(seq) # TODO: parallelize results = [] for qubits in subgraph: state_prep = prog tomo_exp = generate_state_tomography_experiment(state_prep, qubits=qubits) _rs = list(measure_observables(qc, tomo_exp, num_trials)) # Inelegant shim from state tomo refactor. To clean up! expectations=[r.expectation for r in _rs[1:]] variances=[r.std_err ** 2 for r in _rs[1:]] results.append((expectations, variances)) return results
def test_measure_observables_no_symm_calibr_raises_error(forest): qc = get_qc('2q-qvm') exptsetting = ExperimentSetting(plusZ(0), sX(0)) suite = TomographyExperiment([exptsetting], program=Program(I(0)), qubits=[0]) with pytest.raises(ValueError): result = list( measure_observables(qc, suite, n_shots=1000, readout_symmetrize=None, calibrate_readout='plus-eig'))
def test_sic_process_tomo(forest): qc = get_qc('2q-qvm') process = Program(X(0)) settings = [] for in_state in [SIC0, SIC1, SIC2, SIC3]: for out_op in [sI, sX, sY, sZ]: settings += [ExperimentSetting( in_state=in_state(q=0), out_operator=out_op(q=0) )] experiment = TomographyExperiment(settings=settings, program=process, qubits=[0]) results = list(measure_observables(qc, experiment)) assert len(results) == 4 * 4
def test_measure_observables(forest): expts = [ ExperimentSetting(sI(), o1 * o2) for o1, o2 in itertools.product([sI(0), sX(0), sY(0), sZ(0)], [sI(1), sX(1), sY(1), sZ(1)]) ] suite = TomographyExperiment(expts, program=Program(X(0), CNOT(0, 1)), qubits=[0, 1]) assert len(suite) == 4 * 4 gsuite = group_experiments(suite) assert len(gsuite) == 3 * 3 # can get all the terms with I for free in this case qc = get_qc('2q-qvm') for res in measure_observables(qc, gsuite, n_shots=10_000): if res.setting.out_operator in [sI(), sZ(0), sZ(1), sZ(0) * sZ(1)]: assert np.abs(res.expectation) > 0.9 else: assert np.abs(res.expectation) < 0.1
def test_measure_observables_zero_expectation(forest): """ Testing case when expectation value of observable should be close to zero """ qc = get_qc('2q-qvm') exptsetting = ExperimentSetting(plusZ(0), sX(0)) suite = TomographyExperiment([exptsetting], program=Program(I(0)), qubits=[0]) result = list( measure_observables(qc, suite, n_shots=10000, readout_symmetrize='exhaustive', calibrate_readout='plus-eig'))[0] np.testing.assert_almost_equal(result.expectation, 0.0, decimal=1)
def tomographize(qc, program: Program, qubits: List[int], num_shots=1000, t_type='lasso', pauli_num=None): """ Runs tomography on the state generated by program and estimates the state. Can be used as a debugger :param qc: the quantum computer to run the debugger on :param program: which program to run the tomography on :param quibits: whihc qubits to run the tomography debugger on :param num_shots: the number of times to run each tomography experiment to get the expected value :param t_type: which tomography type to use. Possible values: "linear_inv", "mle", "compressed_sensing", "lasso" :param pauli_num: the number of pauli matrices to use in the tomography :return: the density matrix as an ndarray """ # if no pauli_num is specified use the maximum if pauli_num==None: pauli_num=len(Qubits) #Generate experiments qubit_experiments = generate_state_tomography_experiment(program=program, qubits=qubits) exp_list = [] #Experiment holds all 2^n possible pauli matrices for the given number of qubits #Now take pauli_num random pauli matrices as per the paper's advice if (pauli_num > len(qubit_experiments)): print("Cannot sample more Pauli matrices thatn d^2!") return None exp_list = random.sample(list(qubit_experiments), pauli_num) input_exp = PyQuilTomographyExperiment(settings=exp_list, program=program) #Group experiments if possible to minimize QPU runs input_exp = group_experiments(input_exp) #NOTE: Change qvm depending on whether we are simulating qvm qc.compiler.quil_to_native_quil = basic_compile results = list(measure_observables(qc=qc, tomo_experiment=input_exp, n_shots=num_shots)) if t_type == 'compressed_sensing': return compressed_sensing_state_estimate(results=results, qubits=qubits) elif t_type == 'mle': return iterative_mle_state_estimate(results=results, qubits=qubits) elif t_type == "linear_inv": return linear_inv_state_estimate(results=results, qubits=qubits) elif t_type == "lasso": return lasso_state_estimate(results=results, qubits=qubits)
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
def do_tomography(self, arg: str) -> None: """tom(ography) [qubit_index [qubit_index...]] Runs state tomography on the qubits specified by the space-separated list of qubit indices. Without argument, run on all qubits in Program so far (but first ask confirmation). """ qubits = [] if not arg: try: reply = input("Run on all qubits? ") except EOFError: reply = "no" reply = reply.strip().lower() if reply in ("y", "yes"): qubits = list(self.program.get_qubits()) else: try: qubits = [int(x) for x in arg.split()] except ValueError: self.message( "Qubit indices must be specified as a space-separated list" ) return trimmed_program = trim_program(self.program, qubits) if not QuilControlFlowGraph(trimmed_program).is_dag(): raise ValueError("Program is not a dag!") experiment = generate_state_tomography_experiment( trimmed_program, qubits) # TODO: Let user specify n_shots (+ other params) results = list( measure_observables(qc=self.qc, tomo_experiment=experiment, n_shots=1000)) # TODO: Let user specify algorithm rho_est = linear_inv_state_estimate(results, qubits) self.message(np.round(rho_est, 4)) self.message("Purity: {}".format(np.trace(np.matmul(rho_est, rho_est)))) self.recreate_wavefunction(rho_est)
def test_measure_observables_symmetrize(forest): """ Symmetrization alone should not change the outcome on the QVM """ expts = [ ExperimentSetting(sI(), o1 * o2) for o1, o2 in itertools.product([sI(0), sX(0), sY(0), sZ(0)], [sI(1), sX(1), sY(1), sZ(1)]) ] suite = TomographyExperiment(expts, program=Program(X(0), CNOT(0, 1)), qubits=[0, 1]) assert len(suite) == 4 * 4 gsuite = group_experiments(suite) assert len( gsuite) == 3 * 3 # can get all the terms with I for free in this case qc = get_qc('2q-qvm') for res in measure_observables(qc, gsuite, n_shots=10_000, readout_symmetrize='exhaustive'):
def expval(self, observable, wires, par): # Single-qubit observable if len(wires) == 1: # identify Experiment Settings for each of the possible single-qubit observables wire = wires[0] qubit = self.wiring[wire] d_expt_settings = { "Identity": [ExperimentSetting(TensorProductState(), sI(qubit))], "PauliX": [ExperimentSetting(TensorProductState(), sX(qubit))], "PauliY": [ExperimentSetting(TensorProductState(), sY(qubit))], "PauliZ": [ExperimentSetting(TensorProductState(), sZ(qubit))], "Hadamard": [ ExperimentSetting(TensorProductState(), float(np.sqrt(1 / 2)) * sX(qubit)), ExperimentSetting(TensorProductState(), float(np.sqrt(1 / 2)) * sZ(qubit)) ] } # expectation values for single-qubit observables if observable in [ "PauliX", "PauliY", "PauliZ", "Identity", "Hadamard" ]: prep_prog = Program() for instr in self.program.instructions: if isinstance(instr, Gate): # assumes single qubit gates gate, _ = instr.out().split(' ') prep_prog += Program(str(gate) + ' ' + str(qubit)) if self.readout_error is not None: prep_prog.define_noisy_readout(qubit, p00=self.readout_error[0], p11=self.readout_error[1]) tomo_expt = TomographyExperiment( settings=d_expt_settings[observable], program=prep_prog) grouped_tomo_expt = group_experiments(tomo_expt) meas_obs = list( measure_observables( self.qc, grouped_tomo_expt, active_reset=self.active_reset, symmetrize_readout=self.symmetrize_readout, calibrate_readout=self.calibrate_readout)) return np.sum( [expt_result.expectation for expt_result in meas_obs]) elif observable == 'Hermitian': # <H> = \sum_i w_i p_i Hkey = tuple(par[0].flatten().tolist()) w = self._eigs[Hkey]['eigval'] return w[0] * p0 + w[1] * p1 # Multi-qubit observable # ---------------------- # Currently, we only support qml.expval.Hermitian(A, wires), # where A is a 2^N x 2^N matrix acting on N wires. # # Eventually, we will also support tensor products of Pauli # matrices in the PennyLane UI. probs = self.probabilities(wires) if observable == 'Hermitian': Hkey = tuple(par[0].flatten().tolist()) w = self._eigs[Hkey]['eigval'] # <A> = \sum_i w_i p_i return w @ probs
mitigate_readout_errors=True) -> DFEData: """ Acquire data necessary for direct fidelity estimate (DFE). :param qc: A quantum computer object where the experiment will run. :param expr: A partial tomography(``TomographyExperiment``) object describing the experiments to be run. :param n_shots: The minimum number of shots to be taken in each experiment (including calibration). :param active_reset: Boolean flag indicating whether experiments should terminate with an active reset instruction (this can make experiments a lot faster). :param mitigate_readout_errors: Boolean flag indicating whether bias due to imperfect readout should be corrected for :return: a DFE data object :rtype: ``DFEData` """ if mitigate_readout_errors: res = list(measure_observables(qc, expr, n_shots=n_shots, active_reset=active_reset)) else: res = list(measure_observables(qc, expr, n_shots=n_shots, active_reset=active_reset, readout_symmetrize=None, calibrate_readout=None)) return DFEData(results=res, in_states=[str(e[0].in_state) for e in expr], program=expr.program, out_pauli=[str(e[0].out_operator) for e in expr], pauli_point_est=np.array([r.expectation for r in res]), pauli_std_err=np.array([r.stddev for r in res]), cal_point_est=np.array([r.calibration_expectation for r in res]), cal_std_err=np.array([r.calibration_stddev for r in res]), dimension=2**len(expr.qubits), qubits=expr.qubits)
def expval(self, observable): wires = observable.wires # `measure_observables` called only when parametric compilation is turned off if not self.parametric_compilation: # Single-qubit observable if len(wires) == 1: # Ensure sensible observable assert observable.name in [ "PauliX", "PauliY", "PauliZ", "Identity", "Hadamard" ], "Unknown observable" # Create appropriate PauliZ operator wire = wires[0] qubit = self.wiring[wire] pauli_obs = sZ(qubit) # Multi-qubit observable elif len(wires) > 1 and isinstance( observable, Tensor) and not self.parametric_compilation: # All observables are rotated to be measured in the Z-basis, so we just need to # check which wires exist in the observable, map them to physical qubits, and measure # the product of PauliZ operators on those qubits pauli_obs = sI() for wire in observable.wires: qubit = wire pauli_obs *= sZ(self.wiring[qubit]) # Program preparing the state in which to measure observable prep_prog = Program() for instr in self.program.instructions: if isinstance(instr, Gate): # split gate and wires -- assumes 1q and 2q gates tup_gate_wires = instr.out().split(" ") gate = tup_gate_wires[0] str_instr = str(gate) # map wires to qubits for w in tup_gate_wires[1:]: str_instr += f" {int(w)}" prep_prog += Program(str_instr) if self.readout_error is not None: for wire in observable.wires: qubit = wire prep_prog.define_noisy_readout(self.wiring[qubit], p00=self.readout_error[0], p11=self.readout_error[1]) # Measure out multi-qubit observable tomo_expt = Experiment( settings=[ExperimentSetting(TensorProductState(), pauli_obs)], program=prep_prog) grouped_tomo_expt = group_experiments(tomo_expt) meas_obs = list( measure_observables( self.qc, grouped_tomo_expt, active_reset=self.active_reset, symmetrize_readout=self.symmetrize_readout, calibrate_readout=self.calibrate_readout, )) # Return the estimated expectation value return np.sum( [expt_result.expectation for expt_result in meas_obs]) # Calculation of expectation value without using `measure_observables` return super().expval(observable)
def expval(self, observable): wires = observable.wires # Single-qubit observable if len(wires) == 1: # identify Experiment Settings for each of the possible single-qubit observables wire = wires[0] qubit = self.wiring[wire] d_expt_settings = { "Identity": [ExperimentSetting(TensorProductState(), sI(qubit))], "PauliX": [ExperimentSetting(TensorProductState(), sX(qubit))], "PauliY": [ExperimentSetting(TensorProductState(), sY(qubit))], "PauliZ": [ExperimentSetting(TensorProductState(), sZ(qubit))], "Hadamard": [ ExperimentSetting(TensorProductState(), float(np.sqrt(1 / 2)) * sX(qubit)), ExperimentSetting(TensorProductState(), float(np.sqrt(1 / 2)) * sZ(qubit)) ] } # expectation values for single-qubit observables if observable.name in [ "PauliX", "PauliY", "PauliZ", "Identity", "Hadamard" ]: prep_prog = Program() for instr in self.program.instructions: if isinstance(instr, Gate): # split gate and wires -- assumes 1q and 2q gates tup_gate_wires = instr.out().split(' ') gate = tup_gate_wires[0] str_instr = str(gate) # map wires to qubits for w in tup_gate_wires[1:]: str_instr += f' {int(w)}' prep_prog += Program(str_instr) if self.readout_error is not None: prep_prog.define_noisy_readout(qubit, p00=self.readout_error[0], p11=self.readout_error[1]) # All observables are rotated and can be measured in the PauliZ basis tomo_expt = Experiment(settings=d_expt_settings["PauliZ"], program=prep_prog) grouped_tomo_expt = group_experiments(tomo_expt) meas_obs = list( measure_observables( self.qc, grouped_tomo_expt, active_reset=self.active_reset, symmetrize_readout=self.symmetrize_readout, calibrate_readout=self.calibrate_readout)) return np.sum( [expt_result.expectation for expt_result in meas_obs]) elif observable.name == 'Hermitian': # <H> = \sum_i w_i p_i Hkey = tuple(par[0].flatten().tolist()) w = self._eigs[Hkey]['eigval'] return w[0] * p0 + w[1] * p1 return super().expval(observable)