code: def _tf_mixed_norm_solver_bcd_(
      M,
      G,
      Z,
      active_set,
      candidates,
      alpha_space,
      alpha_time,
      lipschitz_constant,
      phi,
      phiT,
      w_space=None,
      w_time=None,
      n_orient=1,
      maxit=200,
      tol=1e-8,
      dgap_freq=10,
      perc=None,
      timeit=True,
      verbose=None,
  ):
      n_sources = G.shape[1]
      n_positions = n_sources // n_orient
  
      # First make G fortran for faster access to blocks of columns
      Gd = np.asfortranarray(G)
      G = np.ascontiguousarray(
          Gd.T.reshape(n_positions, n_orient, -1).transpose(0, 2, 1)
      )
  
      R = M.copy()  # residual
      active = np.where(active_set[::n_orient])[0]
      for idx in active:
          R -= np.dot(G[idx], phiT(Z[idx]))
  
      E = []  # track primal objective function
  
      if w_time is None:
          alpha_time_lc = alpha_time / lipschitz_constant
      else:
          alpha_time_lc = (
              alpha_time * w_time / lipschitz_constant[:, None]
          )
      if w_space is None:
          alpha_space_lc = alpha_space / lipschitz_constant
      else:
          alpha_space_lc = alpha_space * w_space / lipschitz_constant
  
      converged = False
      d_obj = -np.inf
  
      for i in range(maxit):
          for jj in candidates:
              ids = jj * n_orient
              ide = ids + n_orient
  
              G_j = G[jj]
              Z_j = Z[jj]
              active_set_j = active_set[ids:ide]
  
              was_active = np.any(active_set_j)
  
              # gradient step
              GTR = np.dot(G_j.T, R) / lipschitz_constant[jj]
              X_j_new = GTR.copy()
  
              if was_active:
                  X_j = phiT(Z_j)
                  R += np.dot(G_j, X_j)
                  X_j_new += X_j
  
              rows_norm = np.linalg.norm(X_j_new, "fro")
              if rows_norm <= alpha_space_lc[jj]:
                  if was_active:
                      Z[jj] = 0.0
                      active_set_j[:] = False
              else:
                  GTR_phi = phi(GTR)
                  if was_active:
                      Z_j_new = Z_j + GTR_phi
                  else:
                      Z_j_new = GTR_phi
                  col_norm = np.linalg.norm(Z_j_new, axis=0)
  
                  if np.all(col_norm <= alpha_time_lc[jj]):
                      Z[jj] = 0.0
                      active_set_j[:] = False
                  else:
                      # l1
                      shrink = np.maximum(
                          1.0
                          - alpha_time_lc[jj]
                          / np.maximum(col_norm, alpha_time_lc[jj]),
                          0.0,
                      )
                      if w_time is not None:
                          shrink[w_time[jj] == 0.0] = 0.0
                      Z_j_new *= shrink[np.newaxis, :]
  
                      # l21
                      shape_init = Z_j_new.shape
                      row_norm = np.sqrt(phi.norm(Z_j_new, ord=2).sum())
                      if row_norm <= alpha_space_lc[jj]:
                          Z[jj] = 0.0
                          active_set_j[:] = False
                      else:
                          shrink = np.maximum(
                              1.0
                              - alpha_space_lc[jj]
                              / np.maximum(
                                  row_norm, alpha_space_lc[jj]
                              ),
                              0.0,
                          )
                          Z_j_new *= shrink
                          Z[jj] = Z_j_new.reshape(
                              -1, *shape_init[1:]
                          ).copy()
                          active_set_j[:] = True
                          Z_j_phi_T = phiT(Z[jj])
                          R -= np.dot(G_j, Z_j_phi_T)
  
          if (i + 1) % dgap_freq == 0:
              Zd = np.vstack(
                  [
                      Z[pos]
                      for pos in range(n_positions)
                      if np.any(Z[pos])
                  ]
              )
              gap, p_obj, d_obj, _ = dgap_l21l1(
                  M,
                  Gd,
                  Zd,
                  active_set,
                  alpha_space,
                  alpha_time,
                  phi,
                  phiT,
                  n_orient,
                  d_obj,
                  w_space=w_space,
                  w_time=w_time,
              )
              converged = gap < tol
              E.append(p_obj)
              logger.info(
                  "\n    Iteration %d :: n_active %d"
                  % (i + 1, np.sum(active_set) / n_orient)
              )
              logger.info(
                  "    dgap %.2e :: p_obj %f :: d_obj %f"
                  % (gap, p_obj, d_obj)
              )
  
          if converged:
              break
  
          if perc is not None:
              if (
                  np.sum(active_set) / float(n_orient)
                  <= perc * n_positions
              ):
                  break
  
      return Z, active_set, E, converged
  
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 20 for calling a global function with example usage: _tf_mixed_norm_solver_bcd_(M, G, Z, active_set, candidates, alpha_space, alpha_time, lipschitz_constant, phi, phiT, w_space, w_time, n_orient, maxit, tol, dgap_freq, perc, timeit, verbose) and returns: Z,  active_set,  E,  converged