docs: def coarse_grain(self, user_sets):
          r"""Coarse-grains the flux onto user-defined sets.
  
          Parameters
          ----------
          user_sets : list of int-iterables
              sets of states that shall be distinguished in the coarse-grained flux.
  
          Returns
          -------
          (sets, tpt) : (list of int-iterables, tpt-object)
              sets contains the sets tpt is computed on. The tpt states of the new
              tpt object correspond to these sets of states in this order. Sets might
              be identical, if the user has already provided a complete partition that
              respects the boundary between A, B and the intermediates. If not, Sets
              will have more members than provided by the user, containing the
              "remainder" states and reflecting the splitting at the A and B
              boundaries.
              tpt contains a new tpt object for the coarse-grained flux. All its
              quantities (gross_flux, net_flux, A, B, committor, backward_committor)
              are coarse-grained to sets.
  
          Notes
          -----
          All user-specified sets will be split (if necessary) to
          preserve the boundary between A, B and the intermediate
          states.
  
          """
          # coarse-grain sets
          (tpt_sets, Aindexes, Bindexes) = self._compute_coarse_sets(user_sets)
          nnew = len(tpt_sets)
  
          # coarse-grain fluxHere we should branch between sparse and dense implementations, but currently there is only a
          F_coarse = tptapi.coarsegrain(self._gross_flux, tpt_sets)
          Fnet_coarse = tptapi.to_netflux(F_coarse)
  
          # coarse-grain stationary probability and committors - this can be done all dense
          pstat_coarse = np.zeros((nnew))
          forward_committor_coarse = np.zeros((nnew))
          backward_committor_coarse = np.zeros((nnew))
          for i in range(0, nnew):
              I = list(tpt_sets[i])
              muI = self._mu[I]
              pstat_coarse[i] = np.sum(muI)
              partialI = muI / pstat_coarse[i]  # normalized stationary probability over I
              forward_committor_coarse[i] = np.dot(partialI, self._qplus[I])
              backward_committor_coarse[i] = np.dot(partialI, self._qminus[I])
  
          res = ReactiveFlux(Aindexes, Bindexes, Fnet_coarse, mu=pstat_coarse,
                             qminus=backward_committor_coarse, qplus=forward_committor_coarse, gross_flux=F_coarse)
          return (tpt_sets, res)