Ejemplo n.º 1
0
def _tabulate_kak_vectors(
    *,
    already_tabulated: np.ndarray,
    base_gate: np.ndarray,
    max_dist: float,
    kak_mesh: np.ndarray,
    local_unitary_pairs: Sequence[_SingleQubitGatePair],
) -> _TabulationStepResult:
    """Tabulate KAK vectors from products of local unitaries with a base gate.

    Args:
        already_tabulated: Record of which KAK vectors have already been
            tabulated. kak_mesh[i] has been calculated if i is in tabulation.
        base_gate: The base 2 qubit gate used in the gate product.
        max_dist: The largest allowed Pauli error between a generated 2Q
           unitary and a KAK vector mesh point that it is tabulated to.
        kak_mesh: Sequence of KAK vectors filling the Weyl chamber whose
            nearest neighbor distance is about 2*max_error.
        local_unitary_pairs: Sequence of 2-tuples of single qubit unitary
            tensors, each of shape (N,2,2).

    Returns:
        The newly tabulated KAK vectors and the local unitaries used to generate
        them.
    """
    shapes = {pair[0].shape for pair in local_unitary_pairs}
    shapes.update({pair[0].shape for pair in local_unitary_pairs})
    assert len(shapes) == 1
    assert len(shapes.pop()) == 3

    # Generate products
    local_cycles = np.array(
        [vector_kron(*pairs) for pairs in local_unitary_pairs])

    prods = np.einsum('ab,...bc,cd', base_gate, local_cycles[0], base_gate)
    for local_cycle in local_cycles[1:]:
        np.einsum('ab,...bc,...cd', base_gate, local_cycle, prods, out=prods)

    kak_vectors = kak_vector(prods, check_preconditions=False)

    kept_kaks = []
    kept_cycles = []

    for ind, vec in enumerate(kak_vectors):
        # The L2 distance is an upper bound to the locally invariant distance,
        # but it's much faster to compute.
        dists = np.sqrt(np.sum((kak_mesh - vec)**2, axis=-1))
        close = (dists < max_dist).nonzero()[0]
        assert close.shape[0] in (0, 1), f'close.shape: {close.shape}'
        cycles_for_gate = tuple(
            (k_0[ind], k_1[ind]) for k_0, k_1 in local_unitary_pairs)

        # Add the vector and its cycles to the tabulation if it's not already
        # tabulated.
        if not np.all(already_tabulated[close]):
            already_tabulated[close] = True
            kept_kaks.append(vec)
            kept_cycles.append(cycles_for_gate)

    return _TabulationStepResult(kept_kaks, kept_cycles)
Ejemplo n.º 2
0
    def compile_two_qubit_gate(self,
                               unitary: np.ndarray) -> TwoQubitGateCompilation:
        r"""Compute single qubit gates required to compile a desired unitary.

        Given a desired unitary U, this computes the sequence of 1-local gates
        $k_j$ such that the product

        $k_{n-1} A k_{n-2} A ... k_1 A k_0$

        is close to U. Here A is the base_gate of the tabulation.

        Args:
            unitary: Unitary (U above) to compile.

        Returns:
            A TwoQubitGateCompilation object encoding the required local
            unitaries and resulting product above.
        """
        unitary = np.asarray(unitary)
        kak_vec = cirq.kak_vector(unitary, check_preconditions=False)
        infidelities = kak_vector_infidelity(kak_vec,
                                             self.kak_vecs,
                                             ignore_equivalent_vectors=True)
        nearest_ind = infidelities.argmin()

        success = infidelities[nearest_ind] < self.max_expected_infidelity

        # shape (n,2,2,2)
        inner_gates = np.array(self.single_qubit_gates[nearest_ind])

        if inner_gates.size == 0:  # Only need base gate
            kR, kL, actual = _outer_locals_for_unitary(unitary, self.base_gate)
            return TwoQubitGateCompilation(self.base_gate, unitary, (kR, kL),
                                           actual, success)

        # reshape to operators on 2 qubits, (n,4,4)
        inner_gates = vector_kron(inner_gates[..., 0, :, :],
                                  inner_gates[..., 1, :, :])

        assert inner_gates.ndim == 3
        inner_product = reduce(lambda a, b: self.base_gate @ b @ a,
                               inner_gates, self.base_gate)
        kR, kL, actual = _outer_locals_for_unitary(unitary, inner_product)

        out = [kR]
        out.extend(self.single_qubit_gates[nearest_ind])
        out.append(kL)

        return TwoQubitGateCompilation(self.base_gate, unitary, tuple(out),
                                       actual, success)
Ejemplo n.º 3
0
def test_kak_vector_on_weyl_chamber_face():
    # unitaries with KAK vectors from I to ISWAP
    theta_swap = np.linspace(0, np.pi / 4, 10)
    k_vecs = np.zeros((10, 3))
    k_vecs[:, (0, 1)] = theta_swap[:, np.newaxis]

    kwargs = dict(global_phase=1j,
                  single_qubit_operations_before=(X, Y),
                  single_qubit_operations_after=(Z, 1j * X))
    unitaries = np.array([
        cirq.unitary(
            cirq.KakDecomposition(interaction_coefficients=(t, t, 0),
                                  **kwargs)) for t in theta_swap
    ])

    actual = cirq.kak_vector(unitaries)
    np.testing.assert_almost_equal(actual, k_vecs)
Ejemplo n.º 4
0
def test_kak_vector_input_not_unitary():
    with pytest.raises(ValueError, match='must correspond to'):
        cirq.kak_vector(np.zeros((4, 4)))
Ejemplo n.º 5
0
def test_kak_vector_negative_atol():
    with pytest.raises(ValueError, match='must be positive'):
        cirq.kak_vector(np.eye(4), atol=-1.0)
Ejemplo n.º 6
0
def test_kak_vector_wrong_matrix_shape(bad_input):
    with pytest.raises(ValueError, match='to have shape'):
        cirq.kak_vector(bad_input)
Ejemplo n.º 7
0
def test_KAK_vector_weyl_chamber_vertices(unitary, expected):
    actual = cirq.kak_vector(unitary)
    np.testing.assert_almost_equal(actual, expected)
Ejemplo n.º 8
0
def test_KAK_vector_local_invariants_random_input():
    actual = _local_invariants_from_kak(cirq.kak_vector(_random_unitaries))
    expected = _local_invariants_from_kak(_kak_vecs)

    np.testing.assert_almost_equal(actual, expected)
Ejemplo n.º 9
0
def test_kak_vector_matches_vectorized():
    actual = cirq.kak_vector(_random_unitaries)
    expected = np.array([cirq.kak_vector(u) for u in _random_unitaries])
    np.testing.assert_almost_equal(actual, expected)
Ejemplo n.º 10
0
def test_kak_vector_empty():
    assert len(cirq.kak_vector([])) == 0
Ejemplo n.º 11
0
def gate_product_tabulation(
    base_gate: np.ndarray,
    max_infidelity: float,
    *,
    sample_scaling: int = 50,
    allow_missed_points: bool = True,
    random_state: cirq.RANDOM_STATE_OR_SEED_LIKE = None,
) -> GateTabulation:
    r"""Generate a GateTabulation for a base two qubit unitary.

    Args:
        base_gate: The base gate of the tabulation.
        max_infidelity: Sets the desired density of tabulated product unitaries.
            The typical nearest neighbor Euclidean spacing (of the KAK vectors)
            will be on the order of $\sqrt{max\_infidelity}$. Thus the number of
            tabulated points will scale as $max\_infidelity^{-3/2}$.
        sample_scaling: Relative number of random gate products to use in the
            tabulation. The total number of random local unitaries scales as
            ~ $max\_infidelity^{-3/2} * sample\_scaling$. Must be positive.
        random_state: Random state or random state seed.
        allow_missed_points: If True, the tabulation is allowed to conclude
            even if not all points in the Weyl chamber are expected to be
            compilable using 2 or 3 base gates. Otherwise an error is raised
            in this case.

    Returns:
        A GateTabulation object used to compile new two-qubit gates from
        products of the base gate with 1-local unitaries.
    """
    rng = value.parse_random_state(random_state)

    assert 1 / 2 > max_infidelity > 0
    spacing = np.sqrt(max_infidelity / 3)
    mesh_points = weyl_chamber_mesh(spacing)

    # Number of random gate products to sample over in constructing the
    # tabulation. This has to be at least the number of mesh points, as
    # a single product can only be associated with one mesh point.
    assert sample_scaling > 0, 'Input sample_scaling must positive.'
    num_mesh_points = mesh_points.shape[0]
    num_samples = num_mesh_points * sample_scaling

    # include the base gate itself
    kak_vecs = [cirq.kak_vector(base_gate, check_preconditions=False)]
    sq_cycles: List[Tuple[_SingleQubitGatePair, ...]] = [()]

    # Tabulate gates that are close to gates in the mesh
    u_locals_0 = random_qubit_unitary((num_samples, ), rng=rng)
    u_locals_1 = random_qubit_unitary((num_samples, ), rng=rng)

    tabulated_kak_inds = np.zeros((num_mesh_points, ), dtype=bool)

    tabulation_cutoff = 0.5 * spacing
    out = _tabulate_kak_vectors(
        already_tabulated=tabulated_kak_inds,
        base_gate=base_gate,
        max_dist=tabulation_cutoff,
        kak_mesh=mesh_points,
        local_unitary_pairs=[(u_locals_0, u_locals_1)],
    )
    kak_vecs.extend(out.kept_kaks)
    sq_cycles.extend(out.kept_cycles)

    # Will be used later for getting missing KAK vectors.
    kak_vecs_single = np.array(kak_vecs)
    sq_cycles_single = list(sq_cycles)

    summary = (f'Fraction of Weyl chamber reached with 2 gates'
               f': {tabulated_kak_inds.sum() / num_mesh_points :.3f}')

    # repeat for double products
    # Multiply by the same local unitary in the gate product
    out = _tabulate_kak_vectors(
        already_tabulated=tabulated_kak_inds,
        base_gate=base_gate,
        max_dist=tabulation_cutoff,
        kak_mesh=mesh_points,
        local_unitary_pairs=[(u_locals_0, u_locals_1)] * 2,
    )

    kak_vecs.extend(out.kept_kaks)
    sq_cycles.extend(out.kept_cycles)

    summary += (f'\nFraction of Weyl chamber reached with 2 gates and 3 gates'
                f'(same single qubit): '
                f'{tabulated_kak_inds.sum() / num_mesh_points :.3f}')

    # If all KAK vectors in the mesh have been tabulated, return.
    missing_vec_inds = np.logical_not(tabulated_kak_inds).nonzero()[0]

    if not np.any(missing_vec_inds):
        # coverage: ignore
        return GateTabulation(base_gate, np.array(kak_vecs), sq_cycles,
                              max_infidelity, summary, ())

    # Run through remaining KAK vectors that don't have products and try to
    # correct them

    u_locals_0p = random_qubit_unitary((100, ), rng=rng)
    u_locals_1p = random_qubit_unitary((100, ), rng=rng)
    u_locals = vector_kron(u_locals_0p, u_locals_1p)

    # Loop through the mesh points that have not yet been tabulated.
    # Consider their nonlocal parts A and compute products of the form
    # base_gate^\dagger k A
    # Compare the KAK vector of any of those products to the already tabulated
    # KAK vectors from single products of the form
    # base_gate k0 base_gate.
    # If they are close, then base_gate^\dagger k A  ~ base_gate k0 base_gate
    # So we may compute the outer local unitaries kL, kR such that
    #    base_gate^\dagger k A = kL base_gate k0 base_gate kR
    #    A = k^\dagger base_gate kL base_gate k0 base_gate kR
    #    the single-qubit unitary kL is the one we need to get the desired
    #    KAK vector.
    missed_points = []
    base_gate_dag = base_gate.conj().T
    for ind in missing_vec_inds:
        missing_vec = mesh_points[ind]
        # Unitary A we wish to solve for
        missing_unitary = kak_vector_to_unitary(missing_vec)

        # Products of the from base_gate^\dagger k A
        products = np.einsum('ab,...bc,cd', base_gate_dag, u_locals,
                             missing_unitary)
        # KAK vectors for these products
        kaks = cirq.kak_vector(products, check_preconditions=False)
        kaks = kaks[..., np.newaxis, :]

        # Check if any of the product KAK vectors are close to a previously
        # tabulated KAK vector
        dists2 = np.sum((kaks - kak_vecs_single)**2, axis=-1)
        min_dist_inds = np.unravel_index(dists2.argmin(), dists2.shape)
        min_dist = np.sqrt(dists2[min_dist_inds])
        if min_dist < tabulation_cutoff:
            # If so, compute the single qubit unitary k_L such that
            # base_gate^\dagger k A = kL base_gate k0 base_gate kR
            # where k0 is the old (previously tabulated) single qubit unitary
            # and k is one of the single qubit unitaries used above.
            # Indices below are for k, k0 respectively
            new_ind, old_ind = min_dist_inds

            # Special case where the RHS is just base_gate (no single qubit
            # gates yet applied). I.e. base_gate^\dagger k A ~  base_gate
            # which implies  base_gate^\dagger k A = k_L base_gate k_R
            new_product = products[new_ind]
            if old_ind == 0:
                assert not sq_cycles_single[old_ind]
                base_product = base_gate
                _, kL, actual = _outer_locals_for_unitary(
                    new_product, base_product)
                # Add to the enumeration
                sq_cycles.append((kL, ))
            else:  # typical case mentioned above
                assert len(sq_cycles_single[old_ind]) == 1
                old_sq_cycle = sq_cycles_single[old_ind][0]
                old_k = np.kron(*old_sq_cycle)
                base_product = base_gate @ old_k @ base_gate
                _, kL, actual = _outer_locals_for_unitary(
                    new_product, base_product)
                # Add to the enumeration
                sq_cycles.append((old_sq_cycle, kL))

            kak_vecs.append(
                cirq.kak_vector(base_gate @ actual, check_preconditions=False))
        elif not allow_missed_points:
            raise ValueError(
                f'Failed to tabulate a KAK vector near {missing_vec}')
        else:
            missed_points.append(missing_vec)

    kak_vecs = np.array(kak_vecs)
    summary += (f'\nFraction of Weyl chamber reached with 2 gates and 3 gates '
                f'(after patchup)'
                f': {(len(kak_vecs) - 1) / num_mesh_points :.3f}')

    return GateTabulation(base_gate, kak_vecs, sq_cycles, max_infidelity,
                          summary, tuple(missed_points))