Repository: sympy/sympy
File: sympy/integrals/manualintegrate.py (Python)

Code before:
            return DerivativeRule(*integral)
        else:
            return DontKnowRule(integrand, integral.symbol)
    else:
        return ConstantRule(*integral)

def rewrites_rule(integral):
    integrand, symbol = integral

    if integrand.match(1/cos(symbol)):
        rewritten = integrand.subs(1/cos(symbol), sec(symbol))
        return RewriteRule(integrand, symbol, rewritten, integral_steps(rewritten, symbol))

def fallback_rule(integral):
    return DontKnowRule(*integral)

def has_cmplx_exp_pair(expr):
    """
    Check if the expression contains a pair of complex exponentials
    that can be simplified to a sin or cos function.
    """
    exp_args = set()

    for sub in preorder_traversal(expr):
        if sub.func == exp:
            arg = simplify(sub.args[0])
            exp_args.add(arg)

    for a in exp_args:
        if -a in exp_args:
            return True
    return False


def rewrite_exp_hyperbolic(expr):
    a = Wild('a')
    def sinh_replacement(expr):
        return expr.replace(
            exp(a) - exp(-a),
            lambda a: 2*sinh(a)
        )
    def cosh_replacement(expr):
        return expr.replace(
            exp(a) + exp(-a),
            lambda a: 2*cosh(a)
        )

    expr = cosh_replacement(expr)
    expr = sinh_replacement(expr)
    return expr


Review comment (refactor):
The `simplify` function should not be used in library code.

Code after:
    diff_variables = integrand.variables
    undifferentiated_function = integrand.expr
    integrand_variables = undifferentiated_function.free_symbols

    if integral.symbol in integrand_variables:
        if integral.symbol in diff_variables:
            return DerivativeRule(*integral)
        else:
            return DontKnowRule(integrand, integral.symbol)
    else:
        return ConstantRule(*integral)

def rewrites_rule(integral):
    integrand, symbol = integral

    if integrand.match(1/cos(symbol)):
        rewritten = integrand.subs(1/cos(symbol), sec(symbol))
        return RewriteRule(integrand, symbol, rewritten, integral_steps(rewritten, symbol))

def fallback_rule(integral):
    return DontKnowRule(*integral)


# Cache is used to break cyclic integrals.
# Need to use the same dummy variable in cached expressions for them to match.
# Also record "u" of integration by parts, to avoid infinite repetition.
_integral_cache: dict[Expr, Expr | None] = {}
_parts_u_cache: dict[Expr, int] = defaultdict(int)
_cache_dummy = Dummy("z")

def integral_steps(integrand, symbol, **options):
    """Returns the steps needed to compute an integral.

    Explanation
    ===========

    This function attempts to mirror what a student would do by hand as
    closely as possible.

    SymPy Gamma uses this to provide a step-by-step explanation of an
    integral. The code it uses to format the results of this function can be
    found at
    https://github.com/sympy/sympy_gamma/blob/master/app/logic/intsteps.py.

    Examples
    ========

    >>> from sympy import exp, sin
    >>> from sympy.integrals.manualintegrate import integral_steps
    >>> from sympy.abc import x
    >>> print(repr(integral_steps(exp(x) / (1 + exp(2 * x)), x))) \