import numpy as np
from numpy.testing import assert_equal, assert_allclose
from scipy.integrate import odeint
import scipy.integrate._test_odeint_banded as banded5x5


def rhs(y, t):
    dydt = np.zeros_like(y)
    banded5x5.banded5x5(t, y, dydt)
    return dydt


def jac(y, t):
    n = len(y)
    jac = np.zeros((n, n), order='F')
    banded5x5.banded5x5_jac(t, y, 1, 1, jac)
    return jac


def bjac(y, t):
    n = len(y)
    bjac = np.zeros((4, n), order='F')
    banded5x5.banded5x5_bjac(t, y, 1, 1, bjac)
    return bjac


JACTYPE_FULL = 1
JACTYPE_BANDED = 4


def check_odeint(jactype):
    if jactype == JACTYPE_FULL:
        ml = None
        mu = None
        jacobian = jac
    elif jactype == JACTYPE_BANDED:
        ml = 2
        mu = 1
        jacobian = bjac
    else:
        raise ValueError(f"invalid jactype: {jactype!r}")

    y0 = np.arange(1.0, 6.0)
    # These tolerances must match the tolerances used in banded5x5.f.
    rtol = 1e-11
    atol = 1e-13
    dt = 0.125
    nsteps = 64
    t = dt * np.arange(nsteps+1)

    sol, info = odeint(rhs, y0, t,
                       Dfun=jacobian, ml=ml, mu=mu,
                       atol=atol, rtol=rtol, full_output=True)
    yfinal = sol[-1]
    odeint_nst = info['nst'][-1]
    odeint_nfe = info['nfe'][-1]
    odeint_nje = info['nje'][-1]

    y1 = y0.copy()
    # Pure Fortran solution. y1 is modified in-place.
    nst, nfe, nje = banded5x5.banded5x5_solve(y1, nsteps, dt, jactype)

    # It is likely that yfinal and y1 are *exactly* the same, but
    # we'll be cautious and use assert_allclose.
    assert_allclose(yfinal, y1, rtol=1e-12)
    assert_equal((odeint_nst, odeint_nfe, odeint_nje), (nst, nfe, nje))


def test_odeint_full_jac():
    check_odeint(JACTYPE_FULL)


def test_odeint_banded_jac():
    check_odeint(JACTYPE_BANDED)
