Repository: sympy/sympy
File: sympy/assumptions/refine.py (Python)

Code before:
    if ask(Q.even(unknown_parity_coeff), assumptions) or \
        ask(Q.odd(unknown_parity_coeff), assumptions):
        parity_known_coeff += unknown_parity_coeff
        unknown_parity_coeff = 0

    if parity_known_coeff == 0:
        return expr

    # Treat sin as a phase-shifted cosine so a single logic path can handle both.
    if isinstance(expr, sin):
        k = parity_known_coeff - 1
    else:
        k = parity_known_coeff

    # If k is even:
    #    `cos(rem + k*pi/2)` -> `(-1)^(k/2) * cos(rem)`
    #
    # If k is odd:
    #    `cos(rem + k*pi/2)` -> `(-1)^((k+1)/2) * sin(rem)`
    if ask(Q.even(k), assumptions):
        rem = sum(remaining_terms) + unknown_parity_coeff*S.Pi / 2
        return ((-1)**(k / 2)) * cos(rem)
    elif ask(Q.odd(k), assumptions):
        rem = sum(remaining_terms) + unknown_parity_coeff*S.Pi / 2
        return ((-1)**((k + 1) / 2)) * sin(rem)
    return expr

handlers_dict: dict[str, Callable[[Basic, Boolean | bool], Expr]] = {
    'Abs': refine_abs,
    'Pow': refine_Pow,
    'atan2': refine_atan2,
    're': refine_re,
    'im': refine_im,
    'arg': refine_arg,
    'sign': refine_sign,
    'MatrixElement': refine_matrixelement,
    'cos': refine_sin_cos,
    'sin': refine_sin_cos,
}


Review comment (performance):
I don't think these calls to `ask` are needed. Calling ask can be kinda expensive so it's best to avoid calling it when we don't need to.

We should be able to use the information from these calls to figure it out:
```python
        if ask(Q.even(coeff), assumptions) or ask(Q.odd(coeff), assumptions):
```
k is just a sum of terms that all have known parity. We can figure out what the parity of k is by counting how many odd terms are in its sum. 

Actually, I realized that we don't even need to make two calls to ask in that if statement. We know `coeff` is an integer. So if `ask(Q.even(coeff), assumptions)` gives false we know `coeff` must be odd since integers are always either even or odd. Thus the result from `ask(Q.odd(coeff), assumptions)` gives back no new information. 

Code after:
            sum_of_parity_known_coeffs += coeff
            sum_of_parity_known_coeffs_is_even = (
                sum_of_parity_known_coeffs_is_even == coeff_is_even
            )

    if sum_of_parity_known_coeffs == 0:
        return expr

    # Treat sin as a phase-shifted cosine so a single logic path can handle both.
    if isinstance(expr, sin):
        k = sum_of_parity_known_coeffs - 1
        k_is_even = not sum_of_parity_known_coeffs_is_even
    else:
        k = sum_of_parity_known_coeffs
        k_is_even = sum_of_parity_known_coeffs_is_even

    # If k is even:
    #    `cos(rem + k*pi/2)` -> `(-1)^(k/2) * cos(rem)`
    #
    # If k is odd:
    #    `cos(rem + k*pi/2)` -> `(-1)^((k+1)/2) * sin(rem)`
    rem = sum(remaining_terms) + sum_of_parity_unknown_coeffs * S.Pi / 2
    if k_is_even:
        return ((-1)**(k / 2)) * cos(rem)
    else:
        return ((-1)**((k + 1) / 2)) * sin(rem)

handlers_dict: dict[str, Callable[[Basic, Boolean | bool], Expr]] = {
    'Abs': refine_abs,
    'Pow': refine_Pow,
    'atan2': refine_atan2,
    're': refine_re,
    'im': refine_im,
    'arg': refine_arg,
    'sign': refine_sign,
    'MatrixElement': refine_matrixelement,
    'cos': refine_sin_cos,
    'sin': refine_sin_cos,
}