code: def nc_simplify(expr, deep=True):
      """
      Simplify a non-commutative expression composed of multiplication
      and raising to a power by grouping repeated subterms into one power.
      Priority is given to simplifications that give the fewest number
      of arguments in the end (for example, in a*b*a*b*c*a*b*c simplifying
      to (a*b)**2*c*a*b*c gives 5 arguments while a*b*(a*b*c)**2 has 3).
      If ``expr`` is a sum of such terms, the sum of the simplified terms
      is returned.
  
      Keyword argument ``deep`` controls whether or not subexpressions
      nested deeper inside the main expression are simplified. See examples
      below. Setting `deep` to `False` can save time on nested expressions
      that do not need simplifying on all levels.
  
      Examples
      ========
  
      >>> from sympy import symbols
      >>> from sympy.simplify.simplify import nc_simplify
      >>> a, b, c = symbols("a b c", commutative=False)
      >>> nc_simplify(a*b*a*b*c*a*b*c)
      a*b*(a*b*c)**2
      >>> expr = a**2*b*a**4*b*a**4
      >>> nc_simplify(expr)
      a**2*(b*a**4)**2
      >>> nc_simplify(a*b*a*b*c**2*(a*b)**2*c**2)
      ((a*b)**2*c**2)**2
      >>> nc_simplify(a*b*a*b + 2*a*c*a**2*c*a**2*c*a)
      (a*b)**2 + 2*(a*c*a)**3
      >>> nc_simplify(b**-1*a**-1*(a*b)**2)
      a*b
      >>> nc_simplify(a**-1*b**-1*c*a)
      (b*a)**(-1)*c*a
      >>> expr = (a*b*a*b)**2*a*c*a*c
      >>> nc_simplify(expr)
      (a*b)**4*(a*c)**2
      >>> nc_simplify(expr, deep=False)
      (a*b*a*b)**2*(a*c)**2
  
      """
      if isinstance(expr, MatrixExpr):
          expr = expr.doit(inv_expand=False)
          _Add, _Mul, _Pow, _Symbol = (
              MatAdd,
              MatMul,
              MatPow,
              MatrixSymbol,
          )
      else:
          _Add, _Mul, _Pow, _Symbol = Add, Mul, Pow, Symbol
  
      # =========== Auxiliary functions ========================
      def _overlaps(args):
          # Calculate a list of lists m such that m[i][j] contains the lengths
          # of all possible overlaps between args[:i+1] and args[i+1+j:].
          # An overlap is a suffix of the prefix that matches a prefix
          # of the suffix.
          # For example, let expr=c*a*b*a*b*a*b*a*b. Then m[3][0] contains
          # the lengths of overlaps of c*a*b*a*b with a*b*a*b. The overlaps
          # are a*b*a*b, a*b and the empty word so that m[3][0]=[4,2,0].
          # All overlaps rather than only the longest one are recorded
          # because this information helps calculate other overlap lengths.
          m = [[([1, 0] if a == args[0] else [0]) for a in args[1:]]]
          for i in range(1, len(args)):
              overlaps = []
              j = 0
              for j in range(len(args) - i - 1):
                  overlap = []
                  for v in m[i - 1][j + 1]:
                      if (
                          j + i + 1 + v < len(args)
                          and args[i] == args[j + i + 1 + v]
                      ):
                          overlap.append(v + 1)
                  overlap += [0]
                  overlaps.append(overlap)
              m.append(overlaps)
          return m
  
      def _reduce_inverses(_args):
          # replace consecutive negative powers by an inverse
          # of a product of positive powers, e.g. a**-1*b**-1*c
          # will simplify to (a*b)**-1*c;
          # return that new args list and the number of negative
          # powers in it (inv_tot)
          inv_tot = 0  # total number of inverses
          inverses = []
          args = []
          for arg in _args:
              if (
                  isinstance(arg, _Pow)
                  and arg.args[1].is_extended_negative
              ):
                  inverses = [arg**-1] + inverses
                  inv_tot += 1
              else:
                  if len(inverses) == 1:
                      args.append(inverses[0] ** -1)
                  elif len(inverses) > 1:
                      args.append(_Pow(_Mul(*inverses), -1))
                      inv_tot -= len(inverses) - 1
                  inverses = []
                  args.append(arg)
          if inverses:
              args.append(_Pow(_Mul(*inverses), -1))
              inv_tot -= len(inverses) - 1
          return inv_tot, tuple(args)
  
      def get_score(s):
          # compute the number of arguments of s
          # (including in nested expressions) overall
          # but ignore exponents
          if isinstance(s, _Pow):
              return get_score(s.args[0])
          elif isinstance(s, (_Add, _Mul)):
              return sum([get_score(a) for a in s.args])
          return 1
  
      def compare(s, alt_s):
          # compare two possible simplifications and return a
          # "better" one
          if s != alt_s and get_score(alt_s) < get_score(s):
              return alt_s
          return s
  
      # ========================================================
  
      if (
          not isinstance(expr, (_Add, _Mul, _Pow))
          or expr.is_commutative
      ):
          return expr
      args = expr.args[:]
      if isinstance(expr, _Pow):
          if deep:
              return _Pow(nc_simplify(args[0]), args[1]).doit()
          else:
              return expr
      elif isinstance(expr, _Add):
          return _Add(*[nc_simplify(a, deep=deep) for a in args]).doit()
      else:
          # get the non-commutative part
          c_args, args = expr.args_cnc()
          com_coeff = Mul(*c_args)
          if com_coeff != 1:
              return com_coeff * nc_simplify(
                  expr / com_coeff, deep=deep
              )
  
      inv_tot, args = _reduce_inverses(args)
      # if most arguments are negative, work with the inverse
      # of the expression, e.g. a**-1*b*a**-1*c**-1 will become
      # (c*a*b**-1*a)**-1 at the end so can work with c*a*b**-1*a
      invert = False
      if inv_tot > len(args) / 2:
          invert = True
          args = [a**-1 for a in args[::-1]]
  
      if deep:
          args = tuple(nc_simplify(a) for a in args)
  
      m = _overlaps(args)
  
      # simps will be {subterm: end} where `end` is the ending
      # index of a sequence of repetitions of subterm;
      # this is for not wasting time with subterms that are part
      # of longer, already considered sequences
      simps = {}
  
      post = 1
      pre = 1
  
      # the simplification coefficient is the number of
      # arguments by which contracting a given sequence
      # would reduce the word; e.g. in a*b*a*b*c*a*b*c,
      # contracting a*b*a*b to (a*b)**2 removes 3 arguments
      # while a*b*c*a*b*c to (a*b*c)**2 removes 6. It's
      # better to contract the latter so simplification
      # with a maximum simplification coefficient will be chosen
      max_simp_coeff = 0
      simp = None  # information about future simplification
  
      for i in range(1, len(args)):
          simp_coeff = 0
          l = 0  # length of a subterm
          p = 0  # the power of a subterm
          if i < len(args) - 1:
              rep = m[i][0]
          start = i  # starting index of the repeated sequence
          end = i + 1  # ending index of the repeated sequence
          if i == len(args) - 1 or rep == [0]:
              # no subterm is repeated at this stage, at least as
              # far as the arguments are concerned - there may be
              # a repetition if powers are taken into account
              if isinstance(args[i], _Pow) and not isinstance(
                  args[i].args[0], _Symbol
              ):
                  subterm = args[i].args[0].args
                  l = len(subterm)
                  if args[i - l : i] == subterm:
                      # e.g. a*b in a*b*(a*b)**2 is not repeated
                      # in args (= [a, b, (a*b)**2]) but it
                      # can be matched here
                      p += 1
                      start -= l
                  if args[i + 1 : i + 1 + l] == subterm:
                      # e.g. a*b in (a*b)**2*a*b
                      p += 1
                      end += l
              if p:
                  p += args[i].args[1]
              else:
                  continue
          else:
              l = rep[
                  0
              ]  # length of the longest repeated subterm at this point
              start -= l - 1
              subterm = args[start:end]
              p = 2
              end += l
  
          if subterm in simps and simps[subterm] >= start:
              # the subterm is part of a sequence that
              # has already been considered
              continue
  
          # count how many times it's repeated
          while end < len(args):
              if l in m[end - 1][0]:
                  p += 1
                  end += l
              elif (
                  isinstance(args[end], _Pow)
                  and args[end].args[0].args == subterm
              ):
                  # for cases like a*b*a*b*(a*b)**2*a*b
                  p += args[end].args[1]
                  end += 1
              else:
                  break
  
          # see if another match can be made, e.g.
          # for b*a**2 in b*a**2*b*a**3 or a*b in
          # a**2*b*a*b
  
          pre_exp = 0
          pre_arg = 1
          if (
              start - l >= 0
              and args[start - l + 1 : start] == subterm[1:]
          ):
              if isinstance(subterm[0], _Pow):
                  pre_arg = subterm[0].args[0]
                  exp = subterm[0].args[1]
              else:
                  pre_arg = subterm[0]
                  exp = 1
              if (
                  isinstance(args[start - l], _Pow)
                  and args[start - l].args[0] == pre_arg
              ):
                  pre_exp = args[start - l].args[1] - exp
                  start -= l
                  p += 1
              elif args[start - l] == pre_arg:
                  pre_exp = 1 - exp
                  start -= l
                  p += 1
  
          post_exp = 0
          post_arg = 1
          if (
              end + l - 1 < len(args)
              and args[end : end + l - 1] == subterm[:-1]
          ):
              if isinstance(subterm[-1], _Pow):
                  post_arg = subterm[-1].args[0]
                  exp = subterm[-1].args[1]
              else:
                  post_arg = subterm[-1]
                  exp = 1
              if (
                  isinstance(args[end + l - 1], _Pow)
                  and args[end + l - 1].args[0] == post_arg
              ):
                  post_exp = args[end + l - 1].args[1] - exp
                  end += l
                  p += 1
              elif args[end + l - 1] == post_arg:
                  post_exp = 1 - exp
                  end += l
                  p += 1
  
          # Consider a*b*a**2*b*a**2*b*a:
          # b*a**2 is explicitly repeated, but note
          # that in this case a*b*a is also repeated
          # so there are two possible simplifications:
          # a*(b*a**2)**3*a**-1 or (a*b*a)**3
          # The latter is obviously simpler.
          # But in a*b*a**2*b**2*a**2 the simplifications are
          # a*(b*a**2)**2 and (a*b*a)**3*a in which case
          # it's better to stick with the shorter subterm
          if post_exp and exp % 2 == 0 and start > 0:
              exp = exp / 2
              _pre_exp = 1
              _post_exp = 1
              if (
                  isinstance(args[start - 1], _Pow)
                  and args[start - 1].args[0] == post_arg
              ):
                  _post_exp = post_exp + exp
                  _pre_exp = args[start - 1].args[1] - exp
              elif args[start - 1] == post_arg:
                  _post_exp = post_exp + exp
                  _pre_exp = 1 - exp
              if _pre_exp == 0 or _post_exp == 0:
                  if not pre_exp:
                      start -= 1
                  post_exp = _post_exp
                  pre_exp = _pre_exp
                  pre_arg = post_arg
                  subterm = (
                      (post_arg**exp,)
                      + subterm[:-1]
                      + (post_arg**exp,)
                  )
  
          simp_coeff += end - start
  
          if post_exp:
              simp_coeff -= 1
          if pre_exp:
              simp_coeff -= 1
  
          simps[subterm] = end
  
          if simp_coeff > max_simp_coeff:
              max_simp_coeff = simp_coeff
              simp = (start, _Mul(*subterm), p, end, l)
              pre = pre_arg**pre_exp
              post = post_arg**post_exp
  
      if simp:
          subterm = _Pow(nc_simplify(simp[1], deep=deep), simp[2])
          pre = nc_simplify(_Mul(*args[: simp[0]]) * pre, deep=deep)
          post = post * nc_simplify(_Mul(*args[simp[3] :]), deep=deep)
          simp = pre * subterm * post
          if pre != 1 or post != 1:
              # new simplifications may be possible but no need
              # to recurse over arguments
              simp = nc_simplify(simp, deep=False)
      else:
          simp = _Mul(*args)
  
      if invert:
          simp = _Pow(simp, -1)
  
      # see if factor_nc(expr) is simplified better
      if not isinstance(expr, MatrixExpr):
          f_expr = factor_nc(expr)
          if f_expr != expr:
              alt_simp = nc_simplify(f_expr, deep=deep)
              simp = compare(simp, alt_simp)
      else:
          simp = simp.doit(inv_expand=False)
      return simp
  
imports: from collections import defaultdict
  from sympy.concrete.products import Product
  from sympy.concrete.summations import Sum
  from sympy.core import Basic, S, Add, Mul, Pow, Symbol, sympify, expand_func, Function, Dummy, Expr, factor_terms, expand_power_exp, Eq
  from sympy.core.exprtools import factor_nc
  from sympy.core.parameters import global_parameters
  from sympy.core.function import expand_log, count_ops, _mexpand, nfloat, expand_mul, expand
  from sympy.core.numbers import Float, I, pi, Rational
  from sympy.core.relational import Relational
  from sympy.core.rules import Transform
  from sympy.core.sorting import ordered
  from sympy.core.sympify import _sympify
  from sympy.core.traversal import bottom_up, walk
  from sympy.functions import gamma, exp, sqrt, log, exp_polar, re
  from sympy.functions.combinatorial.factorials import CombinatorialFunction
  from sympy.functions.elementary.complexes import unpolarify, Abs, sign
  from sympy.functions.elementary.exponential import ExpBase
  from sympy.functions.elementary.hyperbolic import HyperbolicFunction
  from sympy.functions.elementary.integers import ceiling
  from sympy.functions.elementary.piecewise import Piecewise, piecewise_fold, piecewise_simplify
  from sympy.functions.elementary.trigonometric import TrigonometricFunction
  from sympy.functions.special.bessel import BesselBase, besselj, besseli, besselk, bessely, jn
  from sympy.functions.special.tensor_functions import KroneckerDelta
  from sympy.integrals.integrals import Integral
  from sympy.matrices.expressions import MatrixExpr, MatAdd, MatMul, MatPow, MatrixSymbol
  from sympy.polys import together, cancel, factor
  from sympy.polys.numberfields.minpoly import _is_sum_surds, _minimal_polynomial_sq
  from sympy.simplify.combsimp import combsimp
  from sympy.simplify.cse_opts import sub_pre, sub_post
  from sympy.simplify.hyperexpand import hyperexpand
  from sympy.simplify.powsimp import powsimp
  from sympy.simplify.radsimp import radsimp, fraction, collect_abs
  from sympy.simplify.sqrtdenest import sqrtdenest
  from sympy.simplify.trigsimp import trigsimp, exptrigsimp
  from sympy.utilities.decorator import deprecated
  from sympy.utilities.iterables import has_variety, sift, subsets, iterable
  from sympy.utilities.misc import as_int
  import mpmath
label_desc: Use image node_id 24 for calling a global function with example usage: nc_simplify(expr, deep) and returns: simp, m, inv_tot,  tuple, int, s, expr, get_score, alt_s, _Pow, expr, _Add, sum, unknown