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_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 test_single_qubit_gates(qasm_gate: str, cirq_gate: cirq.Gate): qasm = """OPENQASM 2.0; include "qelib1.inc"; qreg q[2]; {0} q[0]; {0} q; """.format(qasm_gate) parser = QasmParser() q0 = cirq.NamedQubit('q_0') q1 = cirq.NamedQubit('q_1') expected_circuit = Circuit( [cirq_gate.on(q0), cirq_gate.on(q0), cirq_gate.on(q1)]) parsed_qasm = parser.parse(qasm) assert parsed_qasm.supportedFormat assert parsed_qasm.qelib1Include ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit) assert parsed_qasm.qregs == {'q': 2}
def test_amplitude_damping_representation_with_choi( gate: Gate, noise: float, circuit_type: str, ): """Tests the representation by comparing exact Choi matrices.""" q = LineQubit(0) ideal_circuit = convert_from_mitiq(Circuit(gate.on(q)), circuit_type) ideal_choi = _circuit_to_choi(Circuit(gate.on(q))) op_rep = _represent_operation_with_amplitude_damping_noise( ideal_circuit, noise, ) choi_components = [] for noisy_op, coeff in op_rep.basis_expansion.items(): implementable_circ = noisy_op.circuit() depolarizing_op = AmplitudeDampingChannel(noise).on(q) # Apply noise after each sequence. # NOTE: noise is not applied after each operation. implementable_circ.append(depolarizing_op) sequence_choi = _operation_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**-8)
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_get_imp_sequences_no_simplify(gate: Gate): q = LineQubit(0) expected_imp_sequences = [ [gate.on(q)], [gate.on(q), X.on(q)], [gate.on(q), Y.on(q)], [gate.on(q), Z.on(q)], ] assert get_imp_sequences(gate.on(q), DECO_DICT) == expected_imp_sequences
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_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_simplify_paulis_in_simple_pauli_deco_dict(gate: Gate): """Tests DECO_DICT_SIMP which is initialized using the 'simplify_paulis' option. This should produce decomposition dictionary in which Pauli sequences are simplified to single Pauli gates. """ qreg = LineQubit.range(2) decomposition_dict = DECO_DICT_SIMP for q in qreg: deco = decomposition_dict[gate.on(q)] _, first_imp_seq = deco[0] assert first_imp_seq == [gate.on(q)] _, second_imp_seq = deco[1] input_times_x = {X: I, Y: Z, Z: Y} assert second_imp_seq == [input_times_x[gate].on(q)]
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_get_coefficients(gate: Gate): q = LineQubit(0) coeffs = get_coefficients(gate.on(q), DECO_DICT) epsilon = BASE_NOISE * 4.0 / 3.0 c_neg = -(1 / 4) * epsilon / (1 - epsilon) c_pos = 1 - 3 * c_neg assert np.isclose(np.sum(coeffs), 1.0) assert np.allclose(coeffs, [c_pos, c_neg, c_neg, c_neg])
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_simple_pauli_deco_dict_single_qubit(gate: Gate): """Tests that the _simple_pauli_deco_dict function returns a decomposition dicitonary which is consistent with a local depolarizing noise model. This is similar to test_simple_pauli_deco_dict_CNOT but applied to single-qubit gates. """ epsilon = BASE_NOISE * 4.0 / 3.0 c_neg = -(1 / 4) * epsilon / (1 - epsilon) c_pos = 1 - 3 * c_neg qreg = LineQubit.range(2) for q in qreg: deco = DECO_DICT[gate.on(q)] first_coefficient, first_imp_seq = deco[0] assert np.isclose(c_pos, first_coefficient) assert first_imp_seq == [gate.on(q)] second_coefficient, second_imp_seq = deco[1] assert np.isclose(c_neg, second_coefficient) assert second_imp_seq == [gate.on(q), X.on(q)]
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 test_gate_decomposition(gate: cirq.Gate, testcase: unittest.TestCase, print_circuit=True, expected_unitary=None): qubits = cirq.LineQubit.range(2) control, target = qubits circuit_compressed = cirq.Circuit() circuit_decomposed = cirq.Circuit() circuit_compressed.append(gate.on(control, target)) circuit_decomposed.append( cirq.decompose(gate.on(control, target), keep=is_a_basic_operation)) if print_circuit: print("Compressed circuit: \n{}".format(circuit_compressed)) print("Decomposed circuit: \n{}".format(circuit_decomposed)) print(cirq.unitary(circuit_compressed).round(3)) print(cirq.unitary(circuit_decomposed).round(3)) testcase.assertTrue( np.allclose((cirq.unitary(circuit_compressed) if expected_unitary is None else expected_unitary), cirq.unitary(circuit_decomposed)))
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 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 random_cliffords( connectivity_graph: nx.Graph, random_state: random.RandomState, two_qubit_gate: cirq.Gate = cirq.CNOT, ) -> cirq.Circuit: """Returns a circuit with a two-qubit Clifford gate applied to each edge in edges, and a random single-qubit Clifford gate applied to every other qubit. Args: connectivity_graph: A graph with the edges for which the two-qubit Clifford gate is to be applied. random_state: Random state to choose Cliffords (uniformly at random). two_qubit_gate: Two-qubit gate to use. """ gates = [ two_qubit_gate.on(cirq.LineQubit(a), cirq.LineQubit(b)) for a, b in list(connectivity_graph.edges) ] qubits = nx.Graph() qubits.add_nodes_from(nx.isolates(connectivity_graph)) gates.extend( list(random_single_cliffords(qubits, random_state).all_operations())) return cirq.Circuit(gates)
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 test_get_one_norm(gate: Gate): q = LineQubit(0) epsilon = BASE_NOISE * 4.0 / 3.0 expected_one_norm = (1.0 + 0.5 * epsilon) / (1.0 - epsilon) assert np.isclose(get_one_norm(gate.on(q), DECO_DICT), expected_one_norm)
def test_get_probabilities(gate: Gate): q = LineQubit(0) probs = get_probabilities(gate.on(q), DECO_DICT) assert all([p >= 0 for p in probs]) assert np.isclose(sum(probs), 1.0)
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))