docs: def _circuit_as_layers(circuit: circuits.Circuit,
                         grouping: _QubitGrouping) -> List[_TransformsThenCzs]:
      """Transforms a circuit into a series of GroupMatrix+CZ layers.
  
      Args:
          circuit: The circuit to transform.
          grouping: How the circuit's qubits are combined into groups.
  
      Returns:
          A list of layers. Each layer has a matrix to apply to each group of
          qubits, and a list of CZs to apply to pairs of qubits crossing
          between groups.
      """
      frontier = {q: 0 for q in circuit.all_qubits()}
  
      layers = []
      while True:
          # Pull within-group operations into per-group matrices.
          any_group_matrices = False
          group_matrices = []
          for g in grouping.groups:
              # Scan for reachable operations contained within the group qubits.
              start_frontier = {q: frontier[q] for q in g}
              end_frontier = circuit.reachable_frontier_from(start_frontier)
              mergeable_ops = circuit.findall_operations_between(start_frontier,
                                                                 end_frontier)
  
              # Advance frontier.
              for q, v in end_frontier.items():
                  frontier[q] = v
  
              # Fold reachable operations into a single group matrix.
              group_matrix = np.eye(1 << len(g)).reshape((2, 2) * len(g))
              if mergeable_ops:
                  any_group_matrices = True
              for _, op in mergeable_ops:
                  group_matrix = linalg.targeted_left_multiply(
                      left_matrix=protocols.unitary(op).reshape(
                          (2, 2) * len(op.qubits)),
                      right_target=group_matrix,
                      target_axes=[grouping.loc(q)[1] for q in op.qubits])
              group_matrices.append(np.transpose(group_matrix.reshape(
                  1 << len(g), 1 << len(g))))
  
          # Scan for reachable CZ operations between groups.
          end_frontier = circuit.reachable_frontier_from(
              frontier,
              is_blocker=lambda op: grouping.all_in_same_group(*op.qubits))
          cz_ops = circuit.findall_operations_between(frontier, end_frontier)
  
          # Advance frontier.
          frontier = end_frontier
  
          # List out qubit index pairs for each CZ.
          cz_indices = []
          for _, cz in cz_ops:
              a, b = cz.qubits
              assert cz == ops.CZ(a, b)
              cz_indices.append((grouping.ind(a), grouping.ind(b)))
  
          # Combine group and CZ operations into a simulation layer.
          if not any_group_matrices and not cz_indices:
              break
          layer = _TransformsThenCzs(group_matrices=group_matrices,
                                     cz_indices=cz_indices)
          layers.append(layer)
  
      # We should have processed the whole circuit.
      assert frontier == {q: len(circuit) for q in circuit.all_qubits()}
  
      return layers