code: def _tf_mixed_norm_solver_bcd_active_set(
      M,
      G,
      alpha_space,
      alpha_time,
      lipschitz_constant,
      phi,
      phiT,
      Z_init=None,
      w_space=None,
      w_time=None,
      n_orient=1,
      maxit=200,
      tol=1e-8,
      dgap_freq=10,
      verbose=None,
  ):
      n_sensors, n_times = M.shape
      n_sources = G.shape[1]
      n_positions = n_sources // n_orient
  
      Z = dict.fromkeys(np.arange(n_positions), 0.0)
      active_set = np.zeros(n_sources, dtype=bool)
      active = []
      if Z_init is not None:
          if Z_init.shape != (n_sources, phi.n_coefs.sum()):
              raise Exception(
                  "Z_init must be None or an array with shape "
                  "(n_sources, n_coefs)."
              )
          for ii in range(n_positions):
              if np.any(Z_init[ii * n_orient : (ii + 1) * n_orient]):
                  active_set[ii * n_orient : (ii + 1) * n_orient] = True
                  active.append(ii)
          if len(active):
              Z.update(
                  dict(
                      zip(
                          active,
                          np.vsplit(Z_init[active_set], len(active)),
                      )
                  )
              )
  
      E = []
      candidates = range(n_positions)
      d_obj = -np.inf
  
      while True:
          # single BCD pass on all positions:
          Z_init = dict.fromkeys(np.arange(n_positions), 0.0)
          Z_init.update(dict(zip(active, Z.values())))
          Z, active_set, E_tmp, _ = _tf_mixed_norm_solver_bcd_(
              M,
              G,
              Z_init,
              active_set,
              candidates,
              alpha_space,
              alpha_time,
              lipschitz_constant,
              phi,
              phiT,
              w_space=w_space,
              w_time=w_time,
              n_orient=n_orient,
              maxit=1,
              tol=tol,
              perc=None,
              verbose=verbose,
          )
  
          E += E_tmp
  
          # multiple BCD pass on active positions:
          active = np.where(active_set[::n_orient])[0]
          Z_init = dict(
              zip(range(len(active)), [Z[idx] for idx in active])
          )
          candidates_ = range(len(active))
          if w_space is not None:
              w_space_as = w_space[active_set[::n_orient]]
          else:
              w_space_as = None
          if w_time is not None:
              w_time_as = w_time[active_set[::n_orient]]
          else:
              w_time_as = None
  
          Z, as_, E_tmp, converged = _tf_mixed_norm_solver_bcd_(
              M,
              G[:, active_set],
              Z_init,
              np.ones(len(active) * n_orient, dtype=bool),
              candidates_,
              alpha_space,
              alpha_time,
              lipschitz_constant[active_set[::n_orient]],
              phi,
              phiT,
              w_space=w_space_as,
              w_time=w_time_as,
              n_orient=n_orient,
              maxit=maxit,
              tol=tol,
              dgap_freq=dgap_freq,
              perc=0.5,
              verbose=verbose,
          )
          active = np.where(active_set[::n_orient])[0]
          active_set[active_set] = as_.copy()
          E += E_tmp
  
          converged = True
          if converged:
              Zd = np.vstack(
                  [Z[pos] for pos in range(len(Z)) if np.any(Z[pos])]
              )
              gap, p_obj, d_obj, _ = dgap_l21l1(
                  M,
                  G,
                  Zd,
                  active_set,
                  alpha_space,
                  alpha_time,
                  phi,
                  phiT,
                  n_orient,
                  d_obj,
                  w_space,
                  w_time,
              )
              logger.info(
                  "\ndgap %.2e :: p_obj %f :: d_obj %f :: n_active %d"
                  % (gap, p_obj, d_obj, np.sum(active_set) / n_orient)
              )
              if gap < tol:
                  logger.info("\nConvergence reached!\n")
                  break
  
      if active_set.sum():
          Z = np.vstack(
              [Z[pos] for pos in range(len(Z)) if np.any(Z[pos])]
          )
          X = phiT(Z)
      else:
          Z = np.zeros((0, phi.n_coefs.sum()), dtype=np.complex128)
          X = np.zeros((0, n_times))
  
      return X, Z, active_set, E, gap
  
imports: import functools
  from math import sqrt
  import numpy
  from ..time_frequency._stft import istft, stft, stft_norm1, stft_norm2
  from ..utils import _check_option, _get_blas_funcs, _validate_type, logger, sum_squared, verbose, warn
  from .mxne_debiasing import compute_bias
label_desc: Use image node_id 21 for calling a global function with example usage: _tf_mixed_norm_solver_bcd_active_set(M, G, alpha_space, alpha_time, lipschitz_constant, phi, phiT, Z_init, w_space, w_time, n_orient, maxit, tol, dgap_freq, verbose) and returns: X,  Z,  active_set,  E,  gap