def elemwise_collapses(inputs, outputs, out_shape=None, verbose=0):
    """
    This collapse dimensions that are not needed when computing
    elemwise.  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. It handle broadcasted dimensions correctly.

    There is no special handling needed for broadcasted scalar at this level.

    @return: ndims, tuple(dims, strides) after collapsing.
    """
    in_out = inputs + outputs
    del inputs
    if out_shape is not None:
        local_dims = tuple(out_shape)
    else:
        local_dims = tuple(outputs[0].shape)
    del outputs
    nd_orig = len(local_dims)
    if nd_orig == 1:
        all_c_contig = True
        for inp in in_out:
            if not inp.flags['C_CONTIGUOUS'] or inp.shape != local_dims:
                all_c_contig = False
                break
        if all_c_contig:
            return 0, (local_dims, [])
    collapsable = [1] * nd_orig
    local_str = [None] * len(in_out)
    nd_collapse = nd_orig
    for ipos in range(len(in_out)):
        inp = in_out[ipos]
        assert len(inp.shape
            ) == nd_orig, 'All inputs/outputs must have the same number of dimensions. You must broadcast before calling elemwise_collapse'
        local_str[ipos] = list(inp.strides)
        for dim_pos in range(inp.ndim):
            if inp.shape[dim_pos] == 1:
                local_str[ipos][dim_pos] = 0
    if nd_orig == 1:
        return 1, (local_dims, local_str)
    if verbose > 2:
        print('before broadcast collapse')
        print(' nd_collapse', nd_collapse)
        print(' local_dims', local_dims)
        for ipos in range(len(local_str)):
            print(' local_str inputs', ipos, local_str[ipos])
    local_dims = list(local_dims)
    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]
            for input_id in range(len(in_out)):
                for j in range(id + 1, nd_collapse):
                    local_str[input_id][j - 1] = local_str[input_id][j]
            nd_collapse -= 1
            id -= 1
    if verbose > 2:
        print('after broadcast collapse')
        print(' nd_collapse', nd_collapse)
        print(' local_dims', local_dims)
        for ipos in range(len(local_str)):
            print(' local_str inputs', ipos, local_str[ipos])
    nd_collapse_ = [1] * nd_orig
    for ipos in range(len(local_str)):
        strides = local_str[ipos]
        for i in range(nd_collapse - 1, 0, -1):
            if strides[i] * local_dims[i] != strides[i - 1]:
                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]
            for j in range(i + 1, nd_collapse):
                local_dims[j - 1] = local_dims[j]
            for ipos in range(len(local_str)):
                local_str[ipos][i - 1] = local_str[ipos][i]
                for j in range(i + 1, nd_collapse):
                    local_str[ipos][j - 1] = local_str[ipos][j]
            nd_collapse2 -= 1
    nd_collapse = nd_collapse2
    if nd_collapse == 1:
        l = [(local_str[ipos][nd_collapse - 1] == in_out[ipos].itemsize) for
            ipos in range(len(local_str))]
        if all(l):
            nd_collapse = 0
    if verbose:
        print('end collapsing')
        print(' nd_collapse', nd_collapse)
    if verbose > 1:
        print(' local_dims', local_dims)
        for ipos in range(len(local_str)):
            print(' local_str inputs', ipos, local_str[ipos])
    return nd_collapse, (local_dims, local_str)
