Acquaints every triple of qubits.

    Exploits the fact that in a simple linear swap network every pair of
    logical qubits that starts at distance two remains so (except temporarily
    near the edge), and that every third one `goes through` the pair at some
    point in the network. The strategy then iterates through a series of
    mappings in which qubits i and i + k are placed at distance two, for k = 1
    through n / 2. Linear swap networks are used in between to effect the
    permutation.

def cubic_acquaintance_strategy(
        qubits: Iterable[ops.Qid],
        swap_gate: ops.Gate=ops.SWAP
        ) -> circuits.Circuit:
    """Acquaints every triple of qubits.

    Exploits the fact that in a simple linear swap network every pair of
    logical qubits that starts at distance two remains so (except temporarily
    near the edge), and that every third one `goes through` the pair at some
    point in the network. The strategy then iterates through a series of
    mappings in which qubits i and i + k are placed at distance two, for k = 1
    through n / 2. Linear swap networks are used in between to effect the
    permutation.
    """

    qubits = tuple(qubits)
    n_qubits = len(qubits)

    swap_gate = SwapPermutationGate(swap_gate)

    moments = []
    index_order = tuple(range(n_qubits))
    max_separation = max(((n_qubits - 1) // 2) + 1, 2)
    for separation in range(1, max_separation):
        stepped_indices_concatenated = tuple(itertools.chain(*(
                range(offset, n_qubits, separation)
                for offset in range(separation))))
        new_index_order = skip_and_wrap_around(stepped_indices_concatenated)
        permutation = {i: new_index_order.index(j)
            for i, j in enumerate(index_order)}
        permutation_gate = LinearPermutationGate(
                n_qubits, permutation, swap_gate)
        moments.append(ops.Moment([permutation_gate(*qubits)]))
        for i in range(n_qubits + 1):
            for offset in range(3):
                moment = ops.Moment(acquaint(*qubits[j:j+3])
                        for j in range(offset, n_qubits - 2, 3))
                moments.append(moment)
            if i < n_qubits:
                moment = ops.Moment(swap_gate(*qubits[j:j+2])
                        for j in range(i % 2, n_qubits - 1, 2))
                moments.append(moment)
        index_order = new_index_order[::-1]
    return circuits.Circuit(moments, device=UnconstrainedAcquaintanceDevice)