def bottom_up_scan(ex):
    if ex.is_Atom:
        if (ex is I):
            return update_mapping(ex, 2, 1)
        elif (ex is GoldenRatio):
            return bottom_up_scan(ex.expand(func=True))
        elif ex.is_Rational:
            return ex
        elif ex.is_Symbol:
            return ex
    elif (ex.is_Add or ex.is_Mul):
        return ex.func(*[bottom_up_scan(g) for g in ex.args])
    elif ex.is_Pow:
        if ex.exp.is_Rational:
            (base, exp) = (ex.base, ex.exp)
            if exp.is_nonnegative:
                if exp.is_noninteger:
                    (base, exp) = ((base ** exp.numerator), Rational(1, exp.denominator))
                base = bottom_up_scan(base)
            else:
                bmp = PurePoly(minpoly_groebner((1 / base), x, domain=domain), x)
                (base, exp) = (update_mapping((1 / base), bmp), (- exp))
            return update_mapping(ex, exp.denominator, (- (base ** exp.numerator)))
    elif (isinstance(ex, RootOf) and ex.poly.domain.is_Numerical):
        if ex.poly.domain.is_IntegerRing:
            return update_mapping(ex, ex.poly)
        else:
            return update_mapping(ex, ex.poly.sqf_norm()[(- 1)])
    elif isinstance(ex, conjugate):
        return update_mapping(ex, minimal_polynomial(ex.args[0], domain=domain, method='groebner'))
    elif isinstance(ex, Abs):
        return bottom_up_scan(sqrt((ex.args[0] * ex.args[0].conjugate())))
    elif isinstance(ex, re):
        return bottom_up_scan(((ex.args[0] + ex.args[0].conjugate()) / 2))
    elif isinstance(ex, im):
        return bottom_up_scan((((ex.args[0] - ex.args[0].conjugate()) / 2) / I))
    raise NotAlgebraic(f"{ex} doesn't seem to be an algebraic number")
def bottom_up_scan(ex):
    if ex.is_Atom:
        if (ex is I):
            return update_mapping(ex, 2, 1)
        elif (ex is GoldenRatio):
            return bottom_up_scan(ex.expand(func=True))
        elif ex.is_Rational:
            return ex
        elif ex.is_Symbol:
            return ex
    elif (ex.is_Add or ex.is_Mul):
        return ex.func(*[bottom_up_scan(g) for g in ex.args])
    elif ex.is_Pow:
        if ex.exp.is_Rational:
            (base, exp) = (ex.base, ex.exp)
            if exp.is_nonnegative:
                if exp.is_noninteger:
                    (base, exp) = ((base ** exp.numerator), Rational(1, exp.denominator))
                base = bottom_up_scan(base)
            else:
                bmp = PurePoly(minpoly_groebner((1 / base), x, domain=domain), x)
                (base, exp) = (update_mapping((1 / base), bmp), (- exp))
            return update_mapping(ex, exp.denominator, (- (base ** exp.numerator)))
    elif (isinstance(ex, RootOf) and ex.poly.domain.is_Numerical):
        if ex.poly.domain.is_IntegerRing:
            return update_mapping(ex, ex.poly)
        else:
            return update_mapping(ex, ex.poly.sqf_norm()[(- 1)])
    elif isinstance(ex, conjugate):
        return update_mapping(ex, minimal_polynomial(ex.args[0], domain=domain, method='groebner'))
    elif isinstance(ex, Abs):
        return bottom_up_scan(sqrt((ex.args[0] * ex.args[0].conjugate())))
    elif isinstance(ex, re):
        return bottom_up_scan(((ex.args[0] + ex.args[0].conjugate()) / 2))
    elif isinstance(ex, im):
        return bottom_up_scan((((ex.args[0] - ex.args[0].conjugate()) / 2) / I))
    raise NotAlgebraic(f"{ex} doesn't seem to be an algebraic number")