text: from typing import Dict, Callable
  
  from sympy.core import S, Add, Expr, Basic, Mul
  from sympy.logic.boolalg import Boolean
  
  from sympy.assumptions import ask, Q  # type: ignore
  
  
  def refine(expr, assumptions=True):
      """
      Simplify an expression using assumptions.
  
      Explanation
      ===========
  
      Unlike :func:`~.simplify()` which performs structural simplification
      without any assumption, this function transforms the expression into
      the form which is only valid under certain assumptions. Note that
      ``simplify()`` is generally not done in refining process.
  
      Refining boolean expression involves reducing it to ``True`` or
      ``False``. Unlike :func:~.`ask()`, the expression will not be reduced
      if the truth value cannot be determined.
  
      Examples
      ========
  
      >>> from sympy import refine, sqrt, Q
      >>> from sympy.abc import x
      >>> refine(sqrt(x**2), Q.real(x))
      Abs(x)
      >>> refine(sqrt(x**2), Q.positive(x))
      x
  
      >>> refine(Q.real(x), Q.positive(x))
      True
      >>> refine(Q.positive(x), Q.real(x))
      Q.positive(x)
  
      See Also
      ========
  
      sympy.simplify.simplify.simplify : Structural simplification without assumptions.
      sympy.assumptions.ask.ask : Query for boolean expressions using assumptions.
      """
      if not isinstance(expr, Basic):
          return expr
  
      if not expr.is_Atom:
          args = [refine(arg, assumptions) for arg in expr.args]
          # TODO: this will probably not work with Integral or Polynomial
          expr = expr.func(*args)
      if hasattr(expr, '_eval_refine'):
          ref_expr = expr._eval_refine(assumptions)
          if ref_expr is not None:
              return ref_expr
      name = expr.__class__.__name__
      handler = handlers_dict.get(name, None)
      if handler is None:
          return expr
      new_expr = handler(expr, assumptions)
      if (new_expr is None) or (expr == new_expr):
          return expr
      if not isinstance(new_expr, Expr):
          return new_expr
      return refine(new_expr, assumptions)
  
  
  def refine_abs(expr, assumptions):
      """
      Handler for the absolute value.
  
      Examples
      ========
  
      >>> from sympy import Q, Abs
      >>> from sympy.assumptions.refine import refine_abs
      >>> from sympy.abc import x
      >>> refine_abs(Abs(x), Q.real(x))
      >>> refine_abs(Abs(x), Q.positive(x))
      x
      >>> refine_abs(Abs(x), Q.negative(x))
      -x
  
      """
      from sympy.core.logic import fuzzy_not
      from sympy import Abs
      arg = expr.args[0]
      if ask(Q.real(arg), assumptions) and \
              fuzzy_not(ask(Q.negative(arg), assumptions)):
          # if it's nonnegative
          return arg
      if ask(Q.negative(arg), assumptions):
          return -arg
      # arg is Mul
      if isinstance(arg, Mul):
          r = [refine(abs(a), assumptions) for a in arg.args]
          non_abs = []
          in_abs = []
          for i in r:
              if isinstance(i, Abs):
                  in_abs.append(i.args[0])
              else:
                  non_abs.append(i)
          return Mul(*non_abs) * Abs(Mul(*in_abs))
  
  
  def refine_Pow(expr, assumptions):
      """
      Handler for instances of Pow.
  
      Examples
      ========
  
      >>> from sympy import Q
      >>> from sympy.assumptions.refine import refine_Pow
      >>> from sympy.abc import x,y,z
      >>> refine_Pow((-1)**x, Q.real(x))
      >>> refine_Pow((-1)**x, Q.even(x))
      1
      >>> refine_Pow((-1)**x, Q.odd(x))
      -1
  
      For powers of -1, even parts of the exponent can be simplified:
  
      >>> refine_Pow((-1)**(x+y), Q.even(x))
      (-1)**y
      >>> refine_Pow((-1)**(x+y+z), Q.odd(x) & Q.odd(z))
      (-1)**y
      >>> refine_Pow((-1)**(x+y+2), Q.odd(x))
      (-1)**(y + 1)
      >>> refine_Pow((-1)**(x+3), True)
      (-1)**(x + 1)
  
      """
      from sympy.core import Pow, Rational
      from sympy.functions.elementary.complexes import Abs
      from sympy.functions import sign
      if isinstance(expr.base, Abs):
          if ask(Q.real(expr.base.args[0]), assumptions) and \
                  ask(Q.even(expr.exp), assumptions):
              return expr.base.args[0] ** expr.exp
      if ask(Q.real(expr.base), assumptions):
          if expr.base.is_number:
              if ask(Q.even(expr.exp), assumptions):
                  return abs(expr.base) ** expr.exp
              if ask(Q.odd(expr.exp), assumptions):
                  return sign(expr.base) * abs(expr.base) ** expr.exp
          if isinstance(expr.exp, Rational):
              if type(expr.base) is Pow:
                  return abs(expr.base.base) ** (expr.base.exp * expr.exp)
  
          if expr.base is S.NegativeOne:
              if expr.exp.is_Add:
  
                  old = expr
  
                  # For powers of (-1) we can remove
                  #  - even terms
                  #  - pairs of odd terms
                  #  - a single odd term + 1
                  #  - A numerical constant N can be replaced with mod(N,2)
  
                  coeff, terms = expr.exp.as_coeff_add()
                  terms = set(terms)
                  even_terms = set()
                  odd_terms = set()
                  initial_number_of_terms = len(terms)
  
                  for t in terms:
                      if ask(Q.even(t), assumptions):
                          even_terms.add(t)
                      elif ask(Q.odd(t), assumptions):
                          odd_terms.add(t)
  
                  terms -= even_terms
                  if len(odd_terms) % 2:
                      terms -= odd_terms
                      new_coeff = (coeff + S.One) % 2
                  else:
                      terms -= odd_terms
                      new_coeff = coeff % 2
  
                  if new_coeff != coeff or len(terms) < initial_number_of_terms:
                      terms.add(new_coeff)
                      expr = expr.base**(Add(*terms))
  
                  # Handle (-1)**((-1)**n/2 + m/2)
                  e2 = 2*expr.exp
                  if ask(Q.even(e2), assumptions):
                      if e2.could_extract_minus_sign():
                          e2 *= expr.base
                  if e2.is_Add:
                      i, p = e2.as_two_terms()
                      if p.is_Pow and p.base is S.NegativeOne:
                          if ask(Q.integer(p.exp), assumptions):
                              i = (i + 1)/2
                              if ask(Q.even(i), assumptions):
                                  return expr.base**p.exp
                              elif ask(Q.odd(i), assumptions):
                                  return expr.base**(p.exp + 1)
                              else:
                                  return expr.base**(p.exp + i)
  
                  if old != expr:
                      return expr
  
  
  def refine_atan2(expr, assumptions):
      """
      Handler for the atan2 function.
  
      Examples
      ========
  
      >>> from sympy import Q, atan2
      >>> from sympy.assumptions.refine import refine_atan2
      >>> from sympy.abc import x, y
      >>> refine_atan2(atan2(y,x), Q.real(y) & Q.positive(x))
      atan(y/x)
      >>> refine_atan2(atan2(y,x), Q.negative(y) & Q.negative(x))
      atan(y/x) - pi
      >>> refine_atan2(atan2(y,x), Q.positive(y) & Q.negative(x))
      atan(y/x) + pi
      >>> refine_atan2(atan2(y,x), Q.zero(y) & Q.negative(x))
      pi
      >>> refine_atan2(atan2(y,x), Q.positive(y) & Q.zero(x))
      pi/2
      >>> refine_atan2(atan2(y,x), Q.negative(y) & Q.zero(x))
      -pi/2
      >>> refine_atan2(atan2(y,x), Q.zero(y) & Q.zero(x))
      nan
      """
      from sympy.functions.elementary.trigonometric import atan
      from sympy.core import S
      y, x = expr.args
      if ask(Q.real(y) & Q.positive(x), assumptions):
          return atan(y / x)
      elif ask(Q.negative(y) & Q.negative(x), assumptions):
          return atan(y / x) - S.Pi
      elif ask(Q.positive(y) & Q.negative(x), assumptions):
          return atan(y / x) + S.Pi
      elif ask(Q.zero(y) & Q.negative(x), assumptions):
          return S.Pi
      elif ask(Q.positive(y) & Q.zero(x), assumptions):
          return S.Pi/2
      elif ask(Q.negative(y) & Q.zero(x), assumptions):
          return -S.Pi/2
      elif ask(Q.zero(y) & Q.zero(x), assumptions):
          return S.NaN
      else:
          return expr
  
  
  def refine_re(expr, assumptions):
      """
      Handler for real part.
  
      Examples
      ========
  
      >>> from sympy.assumptions.refine import refine_re
      >>> from sympy import Q, re
      >>> from sympy.abc import x
      >>> refine_re(re(x), Q.real(x))
      x
      >>> refine_re(re(x), Q.imaginary(x))
      0
      """
      arg = expr.args[0]
      if ask(Q.real(arg), assumptions):
          return arg
      if ask(Q.imaginary(arg), assumptions):
          return S.Zero
      return _refine_reim(expr, assumptions)
  
  
  def refine_im(expr, assumptions):
      """
      Handler for imaginary part.
  
      Explanation
      ===========
  
      >>> from sympy.assumptions.refine import refine_im
      >>> from sympy import Q, im
      >>> from sympy.abc import x
      >>> refine_im(im(x), Q.real(x))
      0
      >>> refine_im(im(x), Q.imaginary(x))
      -I*x
      """
      arg = expr.args[0]
      if ask(Q.real(arg), assumptions):
          return S.Zero
      if ask(Q.imaginary(arg), assumptions):
          return - S.ImaginaryUnit * arg
      return _refine_reim(expr, assumptions)
  
  def refine_arg(expr, assumptions):
      """
      Handler for complex argument
  
      Explanation
      ===========
  
      >>> from sympy.assumptions.refine import refine_arg
      >>> from sympy import Q, arg
      >>> from sympy.abc import x
      >>> refine_arg(arg(x), Q.positive(x))
      0
      >>> refine_arg(arg(x), Q.negative(x))
      pi
      """
      rg = expr.args[0]
      if ask(Q.positive(rg), assumptions):
          return S.Zero
      if ask(Q.negative(rg), assumptions):
          return S.Pi
      return None
  
  
  def _refine_reim(expr, assumptions):
      # Helper function for refine_re & refine_im
      expanded = expr.expand(complex = True)
      if expanded != expr:
          refined = refine(expanded, assumptions)
          if refined != expanded:
              return refined
      # Best to leave the expression as is
      return None
  
  
  def refine_sign(expr, assumptions):
      """
      Handler for sign.
  
      Examples
      ========
  
      >>> from sympy.assumptions.refine import refine_sign
      >>> from sympy import Symbol, Q, sign, im
      >>> x = Symbol('x', real = True)
      >>> expr = sign(x)
      >>> refine_sign(expr, Q.positive(x) & Q.nonzero(x))
      1
      >>> refine_sign(expr, Q.negative(x) & Q.nonzero(x))
      -1
      >>> refine_sign(expr, Q.zero(x))
      0
      >>> y = Symbol('y', imaginary = True)
      >>> expr = sign(y)
      >>> refine_sign(expr, Q.positive(im(y)))
      I
      >>> refine_sign(expr, Q.negative(im(y)))
      -I
      """
      arg = expr.args[0]
      if ask(Q.zero(arg), assumptions):
          return S.Zero
      if ask(Q.real(arg)):
          if ask(Q.positive(arg), assumptions):
              return S.One
          if ask(Q.negative(arg), assumptions):
              return S.NegativeOne
      if ask(Q.imaginary(arg)):
          arg_re, arg_im = arg.as_real_imag()
          if ask(Q.positive(arg_im), assumptions):
              return S.ImaginaryUnit
          if ask(Q.negative(arg_im), assumptions):
              return -S.ImaginaryUnit
      return expr
  
  
  def refine_matrixelement(expr, assumptions):
      """
      Handler for symmetric part.
  
      Examples
      ========
  
      >>> from sympy.assumptions.refine import refine_matrixelement
      >>> from sympy import Q
      >>> from sympy.matrices.expressions.matexpr import MatrixSymbol
      >>> X = MatrixSymbol('X', 3, 3)
      >>> refine_matrixelement(X[0, 1], Q.symmetric(X))
      X[0, 1]
      >>> refine_matrixelement(X[1, 0], Q.symmetric(X))
      X[0, 1]
      """
      from sympy.matrices.expressions.matexpr import MatrixElement
      matrix, i, j = expr.args
      if ask(Q.symmetric(matrix), assumptions):
          if (i - j).could_extract_minus_sign():
              return expr
          return MatrixElement(matrix, j, i)
  
  handlers_dict = {
      'Abs': refine_abs,
      'Pow': refine_Pow,
      'atan2': refine_atan2,
      're': refine_re,
      'im': refine_im,
      'arg': refine_arg,
      'sign': refine_sign,
      'MatrixElement': refine_matrixelement
  }  # type: Dict[str, Callable[[Expr, Boolean], Expr]]
  