def __init__(self, single_qubit_noise_gate: cirq.Gate, two_qubit_noise_gate: cirq.Gate): if single_qubit_noise_gate.num_qubits() != 1: raise ValueError( 'The noise gate provided to single_qubit_noise_gate has number of qubits != 1.' ) if two_qubit_noise_gate.num_qubits() != 2: raise ValueError( 'The noise gate provided to two_qubit_noise_gate has number of qubits != 2.' ) self.single_qubit_noise_gate = single_qubit_noise_gate self.two_qubit_noise_gate = two_qubit_noise_gate
def test_sample_sequence_choi(gate: cirq.Gate): """Tests the sample_sequence by comparing the exact Choi matrices.""" qreg = cirq.LineQubit.range(gate.num_qubits()) ideal_op = gate.on(*qreg) ideal_circ = cirq.Circuit(ideal_op) noisy_op_tree = [ideal_op] + [cirq.depolarize(BASE_NOISE)(q) for q in qreg] ideal_choi = _operation_to_choi(ideal_op) noisy_choi = _operation_to_choi(noisy_op_tree) representation = represent_operation_with_local_depolarizing_noise( ideal_circ, BASE_NOISE, ) choi_unbiased_estimates = [] rng = np.random.RandomState(1) for _ in range(500): imp_seqs, signs, norm = sample_sequence(ideal_circ, [representation], random_state=rng) noisy_sequence = imp_seqs[0].with_noise(cirq.depolarize(BASE_NOISE)) sequence_choi = _circuit_to_choi(noisy_sequence) choi_unbiased_estimates.append(norm * signs[0] * sequence_choi) choi_pec_estimate = np.average(choi_unbiased_estimates, axis=0) noise_error = np.linalg.norm(ideal_choi - noisy_choi) pec_error = np.linalg.norm(ideal_choi - choi_pec_estimate) assert pec_error < noise_error assert np.allclose(ideal_choi, choi_pec_estimate, atol=0.05)
def assert_unitary_gate_converts_correctly(gate: cirq.Gate): n = gate.num_qubits() for pre, post in solve_tableau(gate).items(): # Create a circuit that measures pre before the gate times post after the gate. # If the gate is translated correctly, the measurement will always be zero. c = stim.Circuit() c.append_operation("H", range(n)) for i in range(n): c.append_operation("CNOT", [i, i + n]) c.append_operation("H", [2 * n]) for q, p in pre.items(): c.append_operation(f"C{p}", [2 * n, q.x]) qs = cirq.LineQubit.range(n) conv_gate, _ = cirq_circuit_to_stim_data(cirq.Circuit(gate(*qs)), q2i={q: q.x for q in qs}) c += conv_gate for q, p in post.items(): c.append_operation(f"C{p}", [2 * n, q.x]) if post.coefficient == -1: c.append_operation("Z", [2 * n]) c.append_operation("H", [2 * n]) c.append_operation("M", [2 * n]) correct = not np.any(c.compile_sampler().sample_bit_packed(10)) assert correct, f"{gate!r} failed to turn {pre} into {post}.\nConverted to:\n{conv_gate}\n"
def test_noisy_gate_conversions_compiled_sampler(gate: cirq.Gate): # Create test circuit that uses superdense coding to quantify arbitrary Pauli error mixtures. n = gate.num_qubits() qs = cirq.LineQubit.range(n) circuit = cirq.Circuit( cirq.H.on_each(qs), [cirq.CNOT(q, q + n) for q in qs], gate(*qs), [cirq.CNOT(q, q + n) for q in qs], cirq.H.on_each(qs), ) expected_rates = cirq.final_density_matrix(circuit).diagonal().real # Convert test circuit to Stim and sample from it. stim_circuit, _ = cirq_circuit_to_stim_data(circuit + cirq.measure( *sorted(circuit.all_qubits())[::-1])) sample_count = 10000 samples = stim_circuit.compile_sampler().sample_bit_packed( sample_count).flat unique, counts = np.unique(samples, return_counts=True) # Compare sample rates to expected rates. for value, count in zip(unique, counts): expected_rate = expected_rates[value] actual_rate = count / sample_count allowed_variation = 5 * (expected_rate * (1 - expected_rate) / sample_count)**0.5 if not 0 <= expected_rate - allowed_variation <= 1: raise ValueError( "Not enough samples to bound results away from extremes.") assert abs(expected_rate - actual_rate) < allowed_variation, ( f"Sample rate {actual_rate} is over 5 standard deviations away from {expected_rate}.\n" f"Gate: {gate}\n" f"Test circuit:\n{circuit}\n" f"Converted circuit:\n{stim_circuit}\n")
def test_biased_noise_representation_with_choi( gate: Gate, epsilon: float, eta: float ): """Tests the representation by comparing exact Choi matrices.""" qreg = LineQubit.range(gate.num_qubits()) ideal_choi = _operation_to_choi(gate.on(*qreg)) op_rep = represent_operation_with_local_biased_noise( Circuit(gate.on(*qreg)), epsilon, eta ) choi_components = [] # Define biased noise channel a = 1 - epsilon b = epsilon * (3 * eta + 1) / (3 * (eta + 1)) c = epsilon / (3 * (eta + 1)) mix = [ (a, unitary(I)), (b, unitary(Z)), (c, unitary(X)), (c, unitary(Y)), ] for noisy_op, coeff in op_rep.basis_expansion.items(): implementable_circ = noisy_op.circuit() # Apply noise after each sequence. # NOTE: noise is not applied after each operation. biased_op = ops.MixedUnitaryChannel(mix).on_each(*qreg) implementable_circ.append(biased_op) sequence_choi = _circuit_to_choi(implementable_circ) choi_components.append(coeff * sequence_choi) combination_choi = np.sum(choi_components, axis=0) assert np.allclose(ideal_choi, combination_choi, atol=10**-6)
def test_sample_sequence_types(gate: Gate): num_qubits = gate.num_qubits() qreg = LineQubit.range(num_qubits) for _ in range(1000): imp_seq, sign, norm = sample_sequence(gate.on(*qreg), DECO_DICT) assert all([isinstance(op, Operation) for op in imp_seq]) assert sign in {1, -1} assert norm > 1
def test_simple_pauli_deco_dict_with_Choi(gate: Gate): """Tests the decomposition by comparing the exact Choi matrices.""" qreg = LineQubit.range(gate.num_qubits()) ideal_choi = _operation_to_choi(gate.on(*qreg)) op_decomp = DECO_DICT[gate.on(*qreg)] choi_components = [] for coeff, imp_seq in op_decomp: # Apply noise after each sequence. # NOTE: noise is not applied after each operation. noisy_sequence = [imp_seq] + [depolarize(BASE_NOISE)(q) for q in qreg] sequence_choi = _operation_to_choi(noisy_sequence) choi_components.append(coeff * sequence_choi) combination_choi = np.sum(choi_components, axis=0) assert np.allclose(ideal_choi, combination_choi)
def test_depolarizing_representation_with_choi(gate: Gate, noise: float): """Tests the representation by comparing exact Choi matrices.""" qreg = LineQubit.range(gate.num_qubits()) ideal_choi = _operation_to_choi(gate.on(*qreg)) op_rep = represent_operation_with_global_depolarizing_noise( Circuit(gate.on(*qreg)), noise, ) choi_components = [] for noisy_op, coeff in op_rep.basis_expansion.items(): implementable_circ = noisy_op.circuit() # Apply noise after each sequence. # NOTE: noise is not applied after each operation. depolarizing_op = DepolarizingChannel(noise, len(qreg))(*qreg) implementable_circ.append(depolarizing_op) sequence_choi = _circuit_to_choi(implementable_circ) choi_components.append(coeff * sequence_choi) combination_choi = np.sum(choi_components, axis=0) assert np.allclose(ideal_choi, combination_choi, atol=10 ** -6)
def test_tableau_simulator_error_mechanisms(gate: cirq.Gate): # Technically this be a test of the `stim` package itself, but it's so convenient to compare to cirq. # Create test circuit that uses superdense coding to quantify arbitrary Pauli error mixtures. n = gate.num_qubits() qs = cirq.LineQubit.range(n) circuit = cirq.Circuit( cirq.H.on_each(qs), [cirq.CNOT(q, q + n) for q in qs], gate(*qs), [cirq.CNOT(q, q + n) for q in qs], cirq.H.on_each(qs), ) expected_rates = cirq.final_density_matrix(circuit).diagonal().real # Convert test circuit to Stim and sample from it. stim_circuit, _ = cirq_circuit_to_stim_data(circuit + cirq.measure( *sorted(circuit.all_qubits())[::-1])) sample_count = 10000 samples = [] for _ in range(sample_count): sim = stim.TableauSimulator() sim.do(stim_circuit) s = 0 for k, v in enumerate(sim.current_measurement_record()): s |= v << k samples.append(s) unique, counts = np.unique(samples, return_counts=True) # Compare sample rates to expected rates. for value, count in zip(unique, counts): expected_rate = expected_rates[value] actual_rate = count / sample_count allowed_variation = 5 * (expected_rate * (1 - expected_rate) / sample_count)**0.5 if not 0 <= expected_rate - allowed_variation <= 1: raise ValueError( "Not enough samples to bound results away from extremes.") assert abs(expected_rate - actual_rate) < allowed_variation, ( f"Sample rate {actual_rate} is over 5 standard deviations away from {expected_rate}.\n" f"Gate: {gate}\n" f"Test circuit:\n{circuit}\n" f"Converted circuit:\n{stim_circuit}\n")
def test_sample_sequence_choi(gate: Gate): """Tests the sample_sequence by comparing the exact Choi matrices.""" qreg = LineQubit.range(gate.num_qubits()) ideal_op = gate.on(*qreg) noisy_op_tree = [ideal_op] + [depolarize(BASE_NOISE)(q) for q in qreg] ideal_choi = _operation_to_choi(gate.on(*qreg)) noisy_choi = _operation_to_choi(noisy_op_tree) choi_unbiased_estimates = [] for _ in range(500): imp_seq, sign, norm = sample_sequence(gate.on(*qreg), DECO_DICT) # Apply noise after each sequence. # NOTE: noise is not applied after each operation. noisy_sequence = [imp_seq] + [depolarize(BASE_NOISE)(q) for q in qreg] sequence_choi = _operation_to_choi(noisy_sequence) choi_unbiased_estimates.append(norm * sign * sequence_choi) choi_pec_estimate = np.average(choi_unbiased_estimates, axis=0) noise_error = np.linalg.norm(ideal_choi - noisy_choi) pec_error = np.linalg.norm(ideal_choi - choi_pec_estimate) assert pec_error < noise_error assert np.allclose(ideal_choi, choi_pec_estimate, atol=0.05)
def solve_tableau(gate: cirq.Gate) -> Dict[cirq.PauliString, cirq.PauliString]: """Computes a stabilizer tableau for the given gate.""" result = {} n = gate.num_qubits() qs = cirq.LineQubit.range(n) for inp in [g(q) for g in [cirq.X, cirq.Z] for q in qs]: # Use superdense coding to extract X and Z flips from the generator conjugated by the gate. c = cirq.Circuit( cirq.H.on_each(qs), [cirq.CNOT(q, q + n) for q in qs], gate(*qs)**-1, inp, gate(*qs), [cirq.CNOT(q, q + n) for q in qs], cirq.H.on_each(qs), [cirq.measure(q, q + n, key=str(q)) for q in qs], ) # Extract X/Y/Z data from sample result (which should be deterministic). s = cirq.Simulator().sample(c) out: cirq.PauliString = cirq.PauliString( {q: "IXZY"[s[str(q)][0]] for q in qs}) # Use phase kickback to determine the sign of the output stabilizer. sign = cirq.NamedQubit('a') c = cirq.Circuit( cirq.H(sign), inp.controlled_by(sign), gate(*qs), out.controlled_by(sign), cirq.H(sign), cirq.measure(sign, key='sign'), ) if cirq.Simulator().sample(c)['sign'][0]: out *= -1 result[inp] = out return result
def add_hadamard_test(state: typing.Union[cirq.Qid, typing.Iterable[cirq.Qid]], gate_v: cirq.Gate, gate_a: cirq.Gate, is_imaginary_part: bool, circuit: cirq.Circuit) -> str: """ Add an Hadamard test for `state` |ψ0> to estimate <ψ0|V+ A V|ψ0>, the circuit of which is: 999: ───H─────S/I─────@──────H──────M('Hadamard test measure 0')─── │ 0: ─────V─────────────A──────────────────────────────────────────── where S/I is set to be the identity gate / the Clifford S gate when `is_imaginary_part` is False / True (i.e. when estimating the real / imaginary part of <ψ0|V+ A V|ψ0>). :param state: The input state |ψ0>. :param gate_v: :param gate_a: :param is_imaginary_part: :param circuit: :return: """ if isinstance(state, cirq.Qid): state = [state] if gate_a.num_qubits() == gate_v.num_qubits() == len(state): pass else: raise ValueError( "The number of qubits acted on by gate `V` and `A` must equal to " "that of `state`, but they are found to be {}, {} and {}.".format( gate_v.num_qubits(), gate_a.num_qubits(), len(state))) auxiliary_qubit = generate_auxiliary_qubit(circuit) measurement_name = "Hadamard test measure " existed_hadamard_measurement_ids = { int(key[len(measurement_name):]) for key in get_all_measurement_keys(circuit) if key.startswith(measurement_name) } hadamard_measurement_id = (max(existed_hadamard_measurement_ids) + 1 if len(existed_hadamard_measurement_ids) != 0 else 0) measurement_name += str(hadamard_measurement_id) circuit.append([cirq.H(auxiliary_qubit), gate_v.on(*state)]) if is_imaginary_part is False: pass else: circuit.append(cirq.S(auxiliary_qubit)) circuit.append([cirq.ControlledGate(gate_a).on(auxiliary_qubit, *state)]) circuit.append(cirq.H(auxiliary_qubit)) circuit.append(cirq.measure(auxiliary_qubit, key=measurement_name)) return measurement_name
def __init__(self, u: cirq.Gate, v: cirq.Gate, n=1): self.u = u self.v = v self.n_phys_qubits = n self.bond_dim = int(2**(u.num_qubits() - 1))