def reduction_collapses(inout, axis, verbose=0):
    """
    This collapse dimensions that are not needed when computing
    reduction.  This is usefull as it lower the indexing computation
    that is heavier on gpu then on cpu.

    This is a generic version. It collapse dimensions at any place in
    the shape.
    @param: inout: tuple(input, output)
    @param: axis: None, interger, list of 1 interger
                  The axis over witch we will do reduction.
    @return: (ndims, (input dims, input strides, input pattern), out strides)
             after collapsing.

    :note: we suppose that we can always collapse the output dimensions.
    """
    input = inout[0]
    out = inout[1]
    if axis is None:
        if input.flags['C_CONTIGUOUS'] or input.flags['F_CONTIGUOUS']:
            return 0, ((input.size,), (input.itemsize,), axis), (0,)
    if input.ndim == 1:
        assert axis == [0] or axis == 0 or axis is None
        return 1, (input.shape, input.strides, axis), (0,)
    if not isinstance(axis, (list, tuple)):
        local_axis = [axis]
    else:
        local_axis = list(axis)
    assert axis is None or len(local_axis) == 1
    local_dims = list(input.shape)
    local_str = list(input.strides)
    out_strides = list(out.strides)
    nd_orig = len(local_dims)
    collapsable = [1] * nd_orig
    nd_collapse = nd_orig
    if verbose > 2:
        print('before broadcast collapse')
        print(' nd_collapse', nd_collapse)
        print(' local_dims', local_dims)
        print(' local_str inputs', local_str)
        print(' local_axis', local_axis)
    for id in range(nd_collapse):
        if local_dims[id] == 1:
            for j in range(id + 1, nd_collapse):
                local_dims[j - 1] = local_dims[j]
                local_str[j - 1] = local_str[j]
                if axis is not None:
                    out_strides[j - 2] = out_strides[j - 1]
            if id in local_axis:
                local_axis.remove(id)
            for axis_pos in range(len(local_axis)):
                if local_axis[axis_pos] > id:
                    local_axis[axis_pos] -= 1
            nd_collapse -= 1
            id -= 1
    if verbose > 2:
        print('after broadcast collapse')
        print(' nd_collapse', nd_collapse)
        print(' local_dims', local_dims)
        print(' local_str inputs', local_str)
        print(' local_axis', local_axis)
        print(' out_strides', out_strides)
    nd_collapse_ = [1] * nd_orig
    for i in range(nd_collapse - 1, 0, -1):
        if local_str[i] * local_dims[i] != local_str[i - 1]:
            nd_collapse_[i] = 0
        elif (i in local_axis) != (i - 1 in local_axis):
            nd_collapse_[i] = 0
    if verbose > 1:
        print('nd_collapse_', nd_collapse_)
    nd_collapse2 = nd_collapse
    for i in range(nd_collapse - 1, 0, -1):
        if nd_collapse_[i] == 1:
            local_dims[i - 1] *= local_dims[i]
            local_str[i - 1] = local_str[i]
            for j in range(i + 1, nd_collapse):
                local_dims[j - 1] = local_dims[j]
                local_str[j - 1] = local_str[j]
                if axis is not None:
                    out_strides[j - 2] = out_strides[j - 1]
            if i in local_axis:
                local_axis.remove(i)
            for axis_pos in range(len(local_axis)):
                if local_axis[axis_pos] > i:
                    local_axis[axis_pos] -= 1
            nd_collapse2 -= 1
    nd_collapse = nd_collapse2
    if nd_collapse == 1:
        if local_str[nd_collapse - 1] == input.itemsize:
            nd_collapse = 0
    if verbose:
        print('end collapsing')
        print(' nd_collapse', nd_collapse)
    if verbose > 1:
        print(' local_dims', local_dims)
        print(' local_str inputs', local_str)
        print(' local_axis', local_axis)
        print(' out_strides', out_strides)
    local_dims = local_dims[:nd_collapse]
    local_str = local_str[:nd_collapse]
    out_strides = out_strides[:nd_collapse]
    return nd_collapse, (local_dims, local_str, local_axis), out_strides
