# -*- cython -*-
#
# Tempita-templated Cython file
#
"""
Fast snippets for LIL matrices.
"""

cimport cython
cimport numpy as cnp
import numpy as np


cnp.import_array()


@cython.wraparound(False)
cpdef lil_get1(cnp.npy_intp M, cnp.npy_intp N, object[:] rows, object[:] datas,
               cnp.npy_intp i, cnp.npy_intp j):
    """
    Get a single item from LIL matrix.

    Doesn't do output type conversion. Checks for bounds errors.

    Parameters
    ----------
    M, N, rows, datas
        Shape and data arrays for a LIL matrix
    i, j : int
        Indices at which to get

    Returns
    -------
    x
        Value at indices.

    """
    cdef list row, data

    if i < -M or i >= M:
        raise IndexError('row index (%d) out of bounds' % (i,))
    if i < 0:
        i += M

    if j < -N or j >= N:
        raise IndexError('column index (%d) out of bounds' % (j,))
    if j < 0:
        j += N

    row = rows[i]
    data = datas[i]
    cdef cnp.npy_intp pos = bisect_left(row, j)

    if pos != len(data) and row[pos] == j:
        return data[pos]
    else:
        return 0


@cython.wraparound(False)
cpdef int lil_insert(cnp.npy_intp M, cnp.npy_intp N, object[:] rows,
                     object[:] datas, cnp.npy_intp i, cnp.npy_intp j,
                     object x) except -1:
    """
    Insert a single item to LIL matrix.

    Checks for bounds errors and deletes item if x is zero.

    Parameters
    ----------
    M, N, rows, datas
        Shape and data arrays for a LIL matrix
    i, j : int
        Indices at which to get
    x
        Value to insert.

    """
    cdef list row, data

    if i < -M or i >= M:
        raise IndexError('row index (%d) out of bounds' % (i,))
    if i < 0:
        i += M

    if j < -N or j >= N:
        raise IndexError('column index (%d) out of bounds' % (j,))
    if j < 0:
        j += N

    row = rows[i]
    data = datas[i]

    cdef cnp.npy_intp pos = bisect_left(row, j)
    if x == 0:
        if pos < len(row) and row[pos] == j:
            del row[pos]
            del data[pos]
    else:
        if pos == len(row):
            row.append(j)
            data.append(x)
        elif row[pos] != j:
            row.insert(pos, j)
            data.insert(pos, x)
        else:
            data[pos] = x


def lil_fancy_get(cnp.npy_intp M, cnp.npy_intp N,
                  object[:] rows,
                  object[:] datas,
                  object[:] new_rows,
                  object[:] new_datas,
                  cnp.ndarray i_idx,
                  cnp.ndarray j_idx):
    """
    Get multiple items at given indices in LIL matrix and store to
    another LIL.

    Parameters
    ----------
    M, N, rows, data
        LIL matrix data, initially empty
    new_rows, new_idx
        Data for LIL matrix to insert to.
        Must be preallocated to shape `i_idx.shape`!
    i_idx, j_idx
        Indices of elements to insert to the new LIL matrix.

    """
    return _LIL_FANCY_GET_DISPATCH[i_idx.dtype](M, N, rows, datas, new_rows, new_datas, i_idx, j_idx)

def _lil_fancy_get_int32(cnp.npy_intp M, cnp.npy_intp N,
                            object[:] rows,
                            object[:] datas,
                            object[:] new_rows,
                            object[:] new_datas,
                            cnp.npy_int32[:,:] i_idx,
                            cnp.npy_int32[:,:] j_idx):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j
    cdef object value
    cdef list new_row
    cdef list new_data

    for x in range(i_idx.shape[0]):
        new_row = []
        new_data = []

        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]

            value = lil_get1(M, N, rows, datas, i, j)

            if value is not 0:
                # Object identity as shortcut
                new_row.append(y)
                new_data.append(value)

        new_rows[x] = new_row
        new_datas[x] = new_data
def _lil_fancy_get_int64(cnp.npy_intp M, cnp.npy_intp N,
                            object[:] rows,
                            object[:] datas,
                            object[:] new_rows,
                            object[:] new_datas,
                            cnp.npy_int64[:,:] i_idx,
                            cnp.npy_int64[:,:] j_idx):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j
    cdef object value
    cdef list new_row
    cdef list new_data

    for x in range(i_idx.shape[0]):
        new_row = []
        new_data = []

        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]

            value = lil_get1(M, N, rows, datas, i, j)

            if value is not 0:
                # Object identity as shortcut
                new_row.append(y)
                new_data.append(value)

        new_rows[x] = new_row
        new_datas[x] = new_data


cdef dict _LIL_FANCY_GET_DISPATCH = {

np.dtype(np.int32): _lil_fancy_get_int32,
np.dtype(np.int64): _lil_fancy_get_int64,
}




def lil_fancy_set(cnp.npy_intp M, cnp.npy_intp N,
                  object[:] rows,
                  object[:] data,
                  cnp.ndarray i_idx,
                  cnp.ndarray j_idx,
                  cnp.ndarray values):
    """
    Set multiple items to a LIL matrix.

    Checks for zero elements and deletes them.

    Parameters
    ----------
    M, N, rows, data
        LIL matrix data
    i_idx, j_idx
        Indices of elements to insert to the new LIL matrix.
    values
        Values of items to set.

    """
    if values.dtype == np.bool_:
        # Cython doesn't support np.bool_ as a memoryview type
        values = values.view(dtype=np.uint8)

    assert i_idx.shape[0] == j_idx.shape[0] and i_idx.shape[1] == j_idx.shape[1]
    return _LIL_FANCY_SET_DISPATCH[i_idx.dtype, values.dtype](M, N, rows, data, i_idx, j_idx, values)

@cython.boundscheck(False)
@cython.wraparound(False)
def _lil_fancy_set_int32_int32(cnp.npy_intp M, cnp.npy_intp N,
                                         object[:] rows,
                                         object[:] data,
                                         cnp.npy_int32[:,:] i_idx,
                                         cnp.npy_int32[:,:] j_idx,
                                         cnp.npy_int32[:,:] values):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j

    for x in range(i_idx.shape[0]):
        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]
            lil_insert(M, N, rows, data, i, j, values[x, y])
@cython.boundscheck(False)
@cython.wraparound(False)
def _lil_fancy_set_int32_int16(cnp.npy_intp M, cnp.npy_intp N,
                                         object[:] rows,
                                         object[:] data,
                                         cnp.npy_int32[:,:] i_idx,
                                         cnp.npy_int32[:,:] j_idx,
                                         cnp.npy_int16[:,:] values):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j

    for x in range(i_idx.shape[0]):
        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]
            lil_insert(M, N, rows, data, i, j, values[x, y])
@cython.boundscheck(False)
@cython.wraparound(False)
def _lil_fancy_set_int32_bool_(cnp.npy_intp M, cnp.npy_intp N,
                                         object[:] rows,
                                         object[:] data,
                                         cnp.npy_int32[:,:] i_idx,
                                         cnp.npy_int32[:,:] j_idx,
                                         cnp.npy_bool[:,:] values):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j

    for x in range(i_idx.shape[0]):
        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]
            lil_insert(M, N, rows, data, i, j, values[x, y])
@cython.boundscheck(False)
@cython.wraparound(False)
def _lil_fancy_set_int32_longdouble(cnp.npy_intp M, cnp.npy_intp N,
                                         object[:] rows,
                                         object[:] data,
                                         cnp.npy_int32[:,:] i_idx,
                                         cnp.npy_int32[:,:] j_idx,
                                         long double[:,:] values):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j

    for x in range(i_idx.shape[0]):
        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]
            lil_insert(M, N, rows, data, i, j, values[x, y])
@cython.boundscheck(False)
@cython.wraparound(False)
def _lil_fancy_set_int32_uint8(cnp.npy_intp M, cnp.npy_intp N,
                                         object[:] rows,
                                         object[:] data,
                                         cnp.npy_int32[:,:] i_idx,
                                         cnp.npy_int32[:,:] j_idx,
                                         cnp.npy_uint8[:,:] values):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j

    for x in range(i_idx.shape[0]):
        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]
            lil_insert(M, N, rows, data, i, j, values[x, y])
@cython.boundscheck(False)
@cython.wraparound(False)
def _lil_fancy_set_int32_complex64(cnp.npy_intp M, cnp.npy_intp N,
                                         object[:] rows,
                                         object[:] data,
                                         cnp.npy_int32[:,:] i_idx,
                                         cnp.npy_int32[:,:] j_idx,
                                         float complex[:,:] values):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j

    for x in range(i_idx.shape[0]):
        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]
            lil_insert(M, N, rows, data, i, j, values[x, y])
@cython.boundscheck(False)
@cython.wraparound(False)
def _lil_fancy_set_int32_int8(cnp.npy_intp M, cnp.npy_intp N,
                                         object[:] rows,
                                         object[:] data,
                                         cnp.npy_int32[:,:] i_idx,
                                         cnp.npy_int32[:,:] j_idx,
                                         cnp.npy_int8[:,:] values):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j

    for x in range(i_idx.shape[0]):
        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]
            lil_insert(M, N, rows, data, i, j, values[x, y])
@cython.boundscheck(False)
@cython.wraparound(False)
def _lil_fancy_set_int32_uint64(cnp.npy_intp M, cnp.npy_intp N,
                                         object[:] rows,
                                         object[:] data,
                                         cnp.npy_int32[:,:] i_idx,
                                         cnp.npy_int32[:,:] j_idx,
                                         cnp.npy_uint64[:,:] values):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j

    for x in range(i_idx.shape[0]):
        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]
            lil_insert(M, N, rows, data, i, j, values[x, y])
@cython.boundscheck(False)
@cython.wraparound(False)
def _lil_fancy_set_int32_float64(cnp.npy_intp M, cnp.npy_intp N,
                                         object[:] rows,
                                         object[:] data,
                                         cnp.npy_int32[:,:] i_idx,
                                         cnp.npy_int32[:,:] j_idx,
                                         cnp.npy_float64[:,:] values):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j

    for x in range(i_idx.shape[0]):
        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]
            lil_insert(M, N, rows, data, i, j, values[x, y])
@cython.boundscheck(False)
@cython.wraparound(False)
def _lil_fancy_set_int32_complex128(cnp.npy_intp M, cnp.npy_intp N,
                                         object[:] rows,
                                         object[:] data,
                                         cnp.npy_int32[:,:] i_idx,
                                         cnp.npy_int32[:,:] j_idx,
                                         double complex[:,:] values):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j

    for x in range(i_idx.shape[0]):
        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]
            lil_insert(M, N, rows, data, i, j, values[x, y])
@cython.boundscheck(False)
@cython.wraparound(False)
def _lil_fancy_set_int32_uint16(cnp.npy_intp M, cnp.npy_intp N,
                                         object[:] rows,
                                         object[:] data,
                                         cnp.npy_int32[:,:] i_idx,
                                         cnp.npy_int32[:,:] j_idx,
                                         cnp.npy_uint16[:,:] values):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j

    for x in range(i_idx.shape[0]):
        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]
            lil_insert(M, N, rows, data, i, j, values[x, y])
@cython.boundscheck(False)
@cython.wraparound(False)
def _lil_fancy_set_int32_int64(cnp.npy_intp M, cnp.npy_intp N,
                                         object[:] rows,
                                         object[:] data,
                                         cnp.npy_int32[:,:] i_idx,
                                         cnp.npy_int32[:,:] j_idx,
                                         cnp.npy_int64[:,:] values):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j

    for x in range(i_idx.shape[0]):
        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]
            lil_insert(M, N, rows, data, i, j, values[x, y])
@cython.boundscheck(False)
@cython.wraparound(False)
def _lil_fancy_set_int32_clongdouble(cnp.npy_intp M, cnp.npy_intp N,
                                         object[:] rows,
                                         object[:] data,
                                         cnp.npy_int32[:,:] i_idx,
                                         cnp.npy_int32[:,:] j_idx,
                                         long double complex[:,:] values):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j

    for x in range(i_idx.shape[0]):
        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]
            lil_insert(M, N, rows, data, i, j, values[x, y])
@cython.boundscheck(False)
@cython.wraparound(False)
def _lil_fancy_set_int32_uint32(cnp.npy_intp M, cnp.npy_intp N,
                                         object[:] rows,
                                         object[:] data,
                                         cnp.npy_int32[:,:] i_idx,
                                         cnp.npy_int32[:,:] j_idx,
                                         cnp.npy_uint32[:,:] values):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j

    for x in range(i_idx.shape[0]):
        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]
            lil_insert(M, N, rows, data, i, j, values[x, y])
@cython.boundscheck(False)
@cython.wraparound(False)
def _lil_fancy_set_int32_float32(cnp.npy_intp M, cnp.npy_intp N,
                                         object[:] rows,
                                         object[:] data,
                                         cnp.npy_int32[:,:] i_idx,
                                         cnp.npy_int32[:,:] j_idx,
                                         cnp.npy_float32[:,:] values):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j

    for x in range(i_idx.shape[0]):
        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]
            lil_insert(M, N, rows, data, i, j, values[x, y])
@cython.boundscheck(False)
@cython.wraparound(False)
def _lil_fancy_set_int64_int32(cnp.npy_intp M, cnp.npy_intp N,
                                         object[:] rows,
                                         object[:] data,
                                         cnp.npy_int64[:,:] i_idx,
                                         cnp.npy_int64[:,:] j_idx,
                                         cnp.npy_int32[:,:] values):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j

    for x in range(i_idx.shape[0]):
        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]
            lil_insert(M, N, rows, data, i, j, values[x, y])
@cython.boundscheck(False)
@cython.wraparound(False)
def _lil_fancy_set_int64_int16(cnp.npy_intp M, cnp.npy_intp N,
                                         object[:] rows,
                                         object[:] data,
                                         cnp.npy_int64[:,:] i_idx,
                                         cnp.npy_int64[:,:] j_idx,
                                         cnp.npy_int16[:,:] values):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j

    for x in range(i_idx.shape[0]):
        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]
            lil_insert(M, N, rows, data, i, j, values[x, y])
@cython.boundscheck(False)
@cython.wraparound(False)
def _lil_fancy_set_int64_bool_(cnp.npy_intp M, cnp.npy_intp N,
                                         object[:] rows,
                                         object[:] data,
                                         cnp.npy_int64[:,:] i_idx,
                                         cnp.npy_int64[:,:] j_idx,
                                         cnp.npy_bool[:,:] values):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j

    for x in range(i_idx.shape[0]):
        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]
            lil_insert(M, N, rows, data, i, j, values[x, y])
@cython.boundscheck(False)
@cython.wraparound(False)
def _lil_fancy_set_int64_longdouble(cnp.npy_intp M, cnp.npy_intp N,
                                         object[:] rows,
                                         object[:] data,
                                         cnp.npy_int64[:,:] i_idx,
                                         cnp.npy_int64[:,:] j_idx,
                                         long double[:,:] values):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j

    for x in range(i_idx.shape[0]):
        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]
            lil_insert(M, N, rows, data, i, j, values[x, y])
@cython.boundscheck(False)
@cython.wraparound(False)
def _lil_fancy_set_int64_uint8(cnp.npy_intp M, cnp.npy_intp N,
                                         object[:] rows,
                                         object[:] data,
                                         cnp.npy_int64[:,:] i_idx,
                                         cnp.npy_int64[:,:] j_idx,
                                         cnp.npy_uint8[:,:] values):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j

    for x in range(i_idx.shape[0]):
        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]
            lil_insert(M, N, rows, data, i, j, values[x, y])
@cython.boundscheck(False)
@cython.wraparound(False)
def _lil_fancy_set_int64_complex64(cnp.npy_intp M, cnp.npy_intp N,
                                         object[:] rows,
                                         object[:] data,
                                         cnp.npy_int64[:,:] i_idx,
                                         cnp.npy_int64[:,:] j_idx,
                                         float complex[:,:] values):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j

    for x in range(i_idx.shape[0]):
        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]
            lil_insert(M, N, rows, data, i, j, values[x, y])
@cython.boundscheck(False)
@cython.wraparound(False)
def _lil_fancy_set_int64_int8(cnp.npy_intp M, cnp.npy_intp N,
                                         object[:] rows,
                                         object[:] data,
                                         cnp.npy_int64[:,:] i_idx,
                                         cnp.npy_int64[:,:] j_idx,
                                         cnp.npy_int8[:,:] values):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j

    for x in range(i_idx.shape[0]):
        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]
            lil_insert(M, N, rows, data, i, j, values[x, y])
@cython.boundscheck(False)
@cython.wraparound(False)
def _lil_fancy_set_int64_uint64(cnp.npy_intp M, cnp.npy_intp N,
                                         object[:] rows,
                                         object[:] data,
                                         cnp.npy_int64[:,:] i_idx,
                                         cnp.npy_int64[:,:] j_idx,
                                         cnp.npy_uint64[:,:] values):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j

    for x in range(i_idx.shape[0]):
        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]
            lil_insert(M, N, rows, data, i, j, values[x, y])
@cython.boundscheck(False)
@cython.wraparound(False)
def _lil_fancy_set_int64_float64(cnp.npy_intp M, cnp.npy_intp N,
                                         object[:] rows,
                                         object[:] data,
                                         cnp.npy_int64[:,:] i_idx,
                                         cnp.npy_int64[:,:] j_idx,
                                         cnp.npy_float64[:,:] values):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j

    for x in range(i_idx.shape[0]):
        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]
            lil_insert(M, N, rows, data, i, j, values[x, y])
@cython.boundscheck(False)
@cython.wraparound(False)
def _lil_fancy_set_int64_complex128(cnp.npy_intp M, cnp.npy_intp N,
                                         object[:] rows,
                                         object[:] data,
                                         cnp.npy_int64[:,:] i_idx,
                                         cnp.npy_int64[:,:] j_idx,
                                         double complex[:,:] values):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j

    for x in range(i_idx.shape[0]):
        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]
            lil_insert(M, N, rows, data, i, j, values[x, y])
@cython.boundscheck(False)
@cython.wraparound(False)
def _lil_fancy_set_int64_uint16(cnp.npy_intp M, cnp.npy_intp N,
                                         object[:] rows,
                                         object[:] data,
                                         cnp.npy_int64[:,:] i_idx,
                                         cnp.npy_int64[:,:] j_idx,
                                         cnp.npy_uint16[:,:] values):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j

    for x in range(i_idx.shape[0]):
        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]
            lil_insert(M, N, rows, data, i, j, values[x, y])
@cython.boundscheck(False)
@cython.wraparound(False)
def _lil_fancy_set_int64_int64(cnp.npy_intp M, cnp.npy_intp N,
                                         object[:] rows,
                                         object[:] data,
                                         cnp.npy_int64[:,:] i_idx,
                                         cnp.npy_int64[:,:] j_idx,
                                         cnp.npy_int64[:,:] values):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j

    for x in range(i_idx.shape[0]):
        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]
            lil_insert(M, N, rows, data, i, j, values[x, y])
@cython.boundscheck(False)
@cython.wraparound(False)
def _lil_fancy_set_int64_clongdouble(cnp.npy_intp M, cnp.npy_intp N,
                                         object[:] rows,
                                         object[:] data,
                                         cnp.npy_int64[:,:] i_idx,
                                         cnp.npy_int64[:,:] j_idx,
                                         long double complex[:,:] values):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j

    for x in range(i_idx.shape[0]):
        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]
            lil_insert(M, N, rows, data, i, j, values[x, y])
@cython.boundscheck(False)
@cython.wraparound(False)
def _lil_fancy_set_int64_uint32(cnp.npy_intp M, cnp.npy_intp N,
                                         object[:] rows,
                                         object[:] data,
                                         cnp.npy_int64[:,:] i_idx,
                                         cnp.npy_int64[:,:] j_idx,
                                         cnp.npy_uint32[:,:] values):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j

    for x in range(i_idx.shape[0]):
        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]
            lil_insert(M, N, rows, data, i, j, values[x, y])
@cython.boundscheck(False)
@cython.wraparound(False)
def _lil_fancy_set_int64_float32(cnp.npy_intp M, cnp.npy_intp N,
                                         object[:] rows,
                                         object[:] data,
                                         cnp.npy_int64[:,:] i_idx,
                                         cnp.npy_int64[:,:] j_idx,
                                         cnp.npy_float32[:,:] values):
    cdef cnp.npy_intp x, y
    cdef cnp.npy_intp i, j

    for x in range(i_idx.shape[0]):
        for y in range(i_idx.shape[1]):
            i = i_idx[x,y]
            j = j_idx[x,y]
            lil_insert(M, N, rows, data, i, j, values[x, y])


cdef dict _LIL_FANCY_SET_DISPATCH = {

(np.dtype(np.int32), np.dtype(np.int32)): _lil_fancy_set_int32_int32,
(np.dtype(np.int32), np.dtype(np.int16)): _lil_fancy_set_int32_int16,
(np.dtype(np.int32), np.dtype(np.bool_)): _lil_fancy_set_int32_bool_,
(np.dtype(np.int32), np.dtype(np.longdouble)): _lil_fancy_set_int32_longdouble,
(np.dtype(np.int32), np.dtype(np.uint8)): _lil_fancy_set_int32_uint8,
(np.dtype(np.int32), np.dtype(np.complex64)): _lil_fancy_set_int32_complex64,
(np.dtype(np.int32), np.dtype(np.int8)): _lil_fancy_set_int32_int8,
(np.dtype(np.int32), np.dtype(np.uint64)): _lil_fancy_set_int32_uint64,
(np.dtype(np.int32), np.dtype(np.float64)): _lil_fancy_set_int32_float64,
(np.dtype(np.int32), np.dtype(np.complex128)): _lil_fancy_set_int32_complex128,
(np.dtype(np.int32), np.dtype(np.uint16)): _lil_fancy_set_int32_uint16,
(np.dtype(np.int32), np.dtype(np.int64)): _lil_fancy_set_int32_int64,
(np.dtype(np.int32), np.dtype(np.clongdouble)): _lil_fancy_set_int32_clongdouble,
(np.dtype(np.int32), np.dtype(np.uint32)): _lil_fancy_set_int32_uint32,
(np.dtype(np.int32), np.dtype(np.float32)): _lil_fancy_set_int32_float32,
(np.dtype(np.int64), np.dtype(np.int32)): _lil_fancy_set_int64_int32,
(np.dtype(np.int64), np.dtype(np.int16)): _lil_fancy_set_int64_int16,
(np.dtype(np.int64), np.dtype(np.bool_)): _lil_fancy_set_int64_bool_,
(np.dtype(np.int64), np.dtype(np.longdouble)): _lil_fancy_set_int64_longdouble,
(np.dtype(np.int64), np.dtype(np.uint8)): _lil_fancy_set_int64_uint8,
(np.dtype(np.int64), np.dtype(np.complex64)): _lil_fancy_set_int64_complex64,
(np.dtype(np.int64), np.dtype(np.int8)): _lil_fancy_set_int64_int8,
(np.dtype(np.int64), np.dtype(np.uint64)): _lil_fancy_set_int64_uint64,
(np.dtype(np.int64), np.dtype(np.float64)): _lil_fancy_set_int64_float64,
(np.dtype(np.int64), np.dtype(np.complex128)): _lil_fancy_set_int64_complex128,
(np.dtype(np.int64), np.dtype(np.uint16)): _lil_fancy_set_int64_uint16,
(np.dtype(np.int64), np.dtype(np.int64)): _lil_fancy_set_int64_int64,
(np.dtype(np.int64), np.dtype(np.clongdouble)): _lil_fancy_set_int64_clongdouble,
(np.dtype(np.int64), np.dtype(np.uint32)): _lil_fancy_set_int64_uint32,
(np.dtype(np.int64), np.dtype(np.float32)): _lil_fancy_set_int64_float32,
}




@cython.cdivision(True)
@cython.boundscheck(False)
@cython.wraparound(False)
cdef inline cnp.npy_intp bisect_left(list a, cnp.npy_intp x) except -1:
    """
    Bisection search in a sorted list.

    List is assumed to contain objects castable to integers.

    Parameters
    ----------
    a
        List to search in
    x
        Value to search for

    Returns
    -------
    j : int
        Index at value (if present), or at the point to which
        it can be inserted maintaining order.

    """
    cdef Py_ssize_t hi = len(a)
    cdef Py_ssize_t lo = 0
    cdef Py_ssize_t mid, v

    while lo < hi:
        mid = lo + (hi - lo) // 2
        v = a[mid]
        if v < x:
            lo = mid + 1
        else:
            hi = mid
    return lo


cdef _fill_dtype_map(map, chars):
    """
    Fill in Numpy dtype chars for problematic types, working around
    Numpy < 1.6 bugs.
    """
    for c in chars:
        if c in "SUVO":
            continue
        dt = np.dtype(c)
        if dt not in map:
            for k, v in map.items():
                if k.kind == dt.kind and k.itemsize == dt.itemsize:
                    map[dt] = v
                    break


cdef _fill_dtype_map2(map):
    """
    Fill in Numpy dtype chars for problematic types, working around
    Numpy < 1.6 bugs.
    """
    for c1 in np.typecodes['Integer']:
        for c2 in np.typecodes['All']:
            if c2 in "SUVO":
                continue
            dt1 = np.dtype(c1)
            dt2 = np.dtype(c2)
            if (dt1, dt2) not in map:
                for k, v in map.items():
                    if (k[0].kind == dt1.kind and k[0].itemsize == dt1.itemsize and
                        k[1].kind == dt2.kind and k[1].itemsize == dt2.itemsize):
                        map[(dt1, dt2)] = v
                        break

_fill_dtype_map(_LIL_FANCY_GET_DISPATCH, np.typecodes['Integer'])
_fill_dtype_map2(_LIL_FANCY_SET_DISPATCH)
