docs: def _get_common_block_structure(lhs_bs, rhs_bs):
      """For two block structures ``aa = (a1, a2, ..., an)``, ``bb = (b1, b2,
      ..., bm)`` generate the maximal common block structure so that every block
      from aa and bb is contained in exactly one block of the resulting
      structure.  This is useful for determining how to apply the distributive
      law when feeding two concatenated Circuit objects into each other.
  
      Examples:
          ``(1, 1, 1), (2, 1) -> (2, 1)``
          ``(1, 1, 2, 1), (2, 1, 2) -> (2, 3)``
  
      Args:
          lhs_bs (tuple): first block structure
          rhs_bs (tuple): second block structure
      """
  
      # for convenience the arguments may also be Circuit objects
      if isinstance(lhs_bs, Circuit):
          lhs_bs = lhs_bs.block_structure
      if isinstance(rhs_bs, Circuit):
          rhs_bs = rhs_bs.block_structure
  
      if sum(lhs_bs) != sum(rhs_bs):
          raise IncompatibleBlockStructures(
              'Blockstructures have different total channel numbers.')
  
      if len(lhs_bs) == len(rhs_bs) == 0:
          return ()
  
      i = j = 1
      lsum = 0
      while True:
          lsum = sum(lhs_bs[:i])
          rsum = sum(rhs_bs[:j])
          if lsum < rsum:
              i += 1
          elif rsum < lsum:
              j += 1
          else:
              break
  
      return (lsum, ) + _get_common_block_structure(lhs_bs[i:], rhs_bs[j:])