feat: add Camera Work AI package

This commit is contained in:
KINDNICK
2026-08-03 04:42:44 +09:00
parent 26c578416f
commit 02d76bc87c
2187 changed files with 119610 additions and 1 deletions
@@ -0,0 +1,41 @@
from cython cimport floating
cpdef enum BLAS_Order:
RowMajor # C contiguous
ColMajor # Fortran contiguous
cpdef enum BLAS_Trans:
NoTrans = 110 # correspond to 'n'
Trans = 116 # correspond to 't'
# BLAS Level 1 ################################################################
cdef floating _dot(int, const floating*, int, const floating*, int) noexcept nogil
cdef floating _asum(int, const floating*, int) noexcept nogil
cdef void _axpy(int, floating, const floating*, int, floating*, int) noexcept nogil
cdef floating _nrm2(int, const floating*, int) noexcept nogil
cdef void _copy(int, const floating*, int, const floating*, int) noexcept nogil
cdef void _scal(int, floating, const floating*, int) noexcept nogil
cdef void _rotg(floating*, floating*, floating*, floating*) noexcept nogil
cdef void _rot(int, floating*, int, floating*, int, floating, floating) noexcept nogil
# BLAS Level 2 ################################################################
cdef void _gemv(BLAS_Order, BLAS_Trans, int, int, floating, const floating*, int,
const floating*, int, floating, floating*, int) noexcept nogil
cdef void _ger(BLAS_Order, int, int, floating, const floating*, int, const floating*,
int, floating*, int) noexcept nogil
# BLASLevel 3 ################################################################
cdef void _gemm(BLAS_Order, BLAS_Trans, BLAS_Trans, int, int, int, floating,
const floating*, int, const floating*, int, floating, floating*,
int) noexcept nogil
@@ -0,0 +1,239 @@
from cython cimport floating
from scipy.linalg.cython_blas cimport sdot, ddot
from scipy.linalg.cython_blas cimport sasum, dasum
from scipy.linalg.cython_blas cimport saxpy, daxpy
from scipy.linalg.cython_blas cimport snrm2, dnrm2
from scipy.linalg.cython_blas cimport scopy, dcopy
from scipy.linalg.cython_blas cimport sscal, dscal
from scipy.linalg.cython_blas cimport srotg, drotg
from scipy.linalg.cython_blas cimport srot, drot
from scipy.linalg.cython_blas cimport sgemv, dgemv
from scipy.linalg.cython_blas cimport sger, dger
from scipy.linalg.cython_blas cimport sgemm, dgemm
################
# BLAS Level 1 #
################
cdef floating _dot(int n, const floating *x, int incx,
const floating *y, int incy) noexcept nogil:
"""x.T.y"""
if floating is float:
return sdot(&n, <float *> x, &incx, <float *> y, &incy)
else:
return ddot(&n, <double *> x, &incx, <double *> y, &incy)
cpdef _dot_memview(const floating[::1] x, const floating[::1] y):
return _dot(x.shape[0], &x[0], 1, &y[0], 1)
cdef floating _asum(int n, const floating *x, int incx) noexcept nogil:
"""sum(|x_i|)"""
if floating is float:
return sasum(&n, <float *> x, &incx)
else:
return dasum(&n, <double *> x, &incx)
cpdef _asum_memview(const floating[::1] x):
return _asum(x.shape[0], &x[0], 1)
cdef void _axpy(int n, floating alpha, const floating *x, int incx,
floating *y, int incy) noexcept nogil:
"""y := alpha * x + y"""
if floating is float:
saxpy(&n, &alpha, <float *> x, &incx, y, &incy)
else:
daxpy(&n, &alpha, <double *> x, &incx, y, &incy)
cpdef _axpy_memview(floating alpha, const floating[::1] x, floating[::1] y):
_axpy(x.shape[0], alpha, &x[0], 1, &y[0], 1)
cdef floating _nrm2(int n, const floating *x, int incx) noexcept nogil:
"""sqrt(sum((x_i)^2))"""
if floating is float:
return snrm2(&n, <float *> x, &incx)
else:
return dnrm2(&n, <double *> x, &incx)
cpdef _nrm2_memview(const floating[::1] x):
return _nrm2(x.shape[0], &x[0], 1)
cdef void _copy(int n, const floating *x, int incx, const floating *y, int incy) noexcept nogil:
"""y := x"""
if floating is float:
scopy(&n, <float *> x, &incx, <float *> y, &incy)
else:
dcopy(&n, <double *> x, &incx, <double *> y, &incy)
cpdef _copy_memview(const floating[::1] x, const floating[::1] y):
_copy(x.shape[0], &x[0], 1, &y[0], 1)
cdef void _scal(int n, floating alpha, const floating *x, int incx) noexcept nogil:
"""x := alpha * x"""
if floating is float:
sscal(&n, &alpha, <float *> x, &incx)
else:
dscal(&n, &alpha, <double *> x, &incx)
cpdef _scal_memview(floating alpha, const floating[::1] x):
_scal(x.shape[0], alpha, &x[0], 1)
cdef void _rotg(floating *a, floating *b, floating *c, floating *s) noexcept nogil:
"""Generate plane rotation"""
if floating is float:
srotg(a, b, c, s)
else:
drotg(a, b, c, s)
cpdef _rotg_memview(floating a, floating b, floating c, floating s):
_rotg(&a, &b, &c, &s)
return a, b, c, s
cdef void _rot(int n, floating *x, int incx, floating *y, int incy,
floating c, floating s) noexcept nogil:
"""Apply plane rotation"""
if floating is float:
srot(&n, x, &incx, y, &incy, &c, &s)
else:
drot(&n, x, &incx, y, &incy, &c, &s)
cpdef _rot_memview(floating[::1] x, floating[::1] y, floating c, floating s):
_rot(x.shape[0], &x[0], 1, &y[0], 1, c, s)
################
# BLAS Level 2 #
################
cdef void _gemv(BLAS_Order order, BLAS_Trans ta, int m, int n, floating alpha,
const floating *A, int lda, const floating *x, int incx,
floating beta, floating *y, int incy) noexcept nogil:
"""y := alpha * op(A).x + beta * y"""
cdef char ta_ = ta
if order == BLAS_Order.RowMajor:
ta_ = BLAS_Trans.NoTrans if ta == BLAS_Trans.Trans else BLAS_Trans.Trans
if floating is float:
sgemv(&ta_, &n, &m, &alpha, <float *> A, &lda, <float *> x,
&incx, &beta, y, &incy)
else:
dgemv(&ta_, &n, &m, &alpha, <double *> A, &lda, <double *> x,
&incx, &beta, y, &incy)
else:
if floating is float:
sgemv(&ta_, &m, &n, &alpha, <float *> A, &lda, <float *> x,
&incx, &beta, y, &incy)
else:
dgemv(&ta_, &m, &n, &alpha, <double *> A, &lda, <double *> x,
&incx, &beta, y, &incy)
cpdef _gemv_memview(BLAS_Trans ta, floating alpha, const floating[:, :] A,
const floating[::1] x, floating beta, floating[::1] y):
cdef:
int m = A.shape[0]
int n = A.shape[1]
BLAS_Order order = (
BLAS_Order.ColMajor if A.strides[0] == A.itemsize else BLAS_Order.RowMajor
)
int lda = m if order == BLAS_Order.ColMajor else n
_gemv(order, ta, m, n, alpha, &A[0, 0], lda, &x[0], 1, beta, &y[0], 1)
cdef void _ger(BLAS_Order order, int m, int n, floating alpha,
const floating *x, int incx, const floating *y,
int incy, floating *A, int lda) noexcept nogil:
"""A := alpha * x.y.T + A"""
if order == BLAS_Order.RowMajor:
if floating is float:
sger(&n, &m, &alpha, <float *> y, &incy, <float *> x, &incx, A, &lda)
else:
dger(&n, &m, &alpha, <double *> y, &incy, <double *> x, &incx, A, &lda)
else:
if floating is float:
sger(&m, &n, &alpha, <float *> x, &incx, <float *> y, &incy, A, &lda)
else:
dger(&m, &n, &alpha, <double *> x, &incx, <double *> y, &incy, A, &lda)
cpdef _ger_memview(floating alpha, const floating[::1] x,
const floating[::1] y, floating[:, :] A):
cdef:
int m = A.shape[0]
int n = A.shape[1]
BLAS_Order order = (
BLAS_Order.ColMajor if A.strides[0] == A.itemsize else BLAS_Order.RowMajor
)
int lda = m if order == BLAS_Order.ColMajor else n
_ger(order, m, n, alpha, &x[0], 1, &y[0], 1, &A[0, 0], lda)
################
# BLAS Level 3 #
################
cdef void _gemm(BLAS_Order order, BLAS_Trans ta, BLAS_Trans tb, int m, int n,
int k, floating alpha, const floating *A, int lda, const floating *B,
int ldb, floating beta, floating *C, int ldc) noexcept nogil:
"""C := alpha * op(A).op(B) + beta * C"""
# TODO: Remove the pointer casts below once SciPy uses const-qualification.
# See: https://github.com/scipy/scipy/issues/14262
cdef:
char ta_ = ta
char tb_ = tb
if order == BLAS_Order.RowMajor:
if floating is float:
sgemm(&tb_, &ta_, &n, &m, &k, &alpha, <float*>B,
&ldb, <float*>A, &lda, &beta, C, &ldc)
else:
dgemm(&tb_, &ta_, &n, &m, &k, &alpha, <double*>B,
&ldb, <double*>A, &lda, &beta, C, &ldc)
else:
if floating is float:
sgemm(&ta_, &tb_, &m, &n, &k, &alpha, <float*>A,
&lda, <float*>B, &ldb, &beta, C, &ldc)
else:
dgemm(&ta_, &tb_, &m, &n, &k, &alpha, <double*>A,
&lda, <double*>B, &ldb, &beta, C, &ldc)
cpdef _gemm_memview(BLAS_Trans ta, BLAS_Trans tb, floating alpha,
const floating[:, :] A, const floating[:, :] B, floating beta,
floating[:, :] C):
cdef:
int m = A.shape[0] if ta == BLAS_Trans.NoTrans else A.shape[1]
int n = B.shape[1] if tb == BLAS_Trans.NoTrans else B.shape[0]
int k = A.shape[1] if ta == BLAS_Trans.NoTrans else A.shape[0]
int lda, ldb, ldc
BLAS_Order order = (
BLAS_Order.ColMajor if A.strides[0] == A.itemsize else BLAS_Order.RowMajor
)
if order == BLAS_Order.RowMajor:
lda = k if ta == BLAS_Trans.NoTrans else m
ldb = n if tb == BLAS_Trans.NoTrans else k
ldc = n
else:
lda = m if ta == BLAS_Trans.NoTrans else k
ldb = k if tb == BLAS_Trans.NoTrans else n
ldc = m
_gemm(order, ta, tb, m, n, k, alpha, &A[0, 0],
lda, &B[0, 0], ldb, beta, &C[0, 0], ldc)
@@ -0,0 +1,19 @@
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
"""
Uses C++ map containers for fast dict-like behavior with keys being
integers, and values float.
"""
from libcpp.map cimport map as cpp_map
from ._typedefs cimport float64_t, intp_t
###############################################################################
# An object to be used in Python
cdef class IntFloatDict:
cdef cpp_map[intp_t, float64_t] my_map
cdef _to_arrays(self, intp_t [:] keys, float64_t [:] values)
@@ -0,0 +1,137 @@
"""
Uses C++ map containers for fast dict-like behavior with keys being
integers, and values float.
"""
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
# C++
from cython.operator cimport dereference as deref, preincrement as inc
from libcpp.utility cimport pair
from libcpp.map cimport map as cpp_map
import numpy as np
from ._typedefs cimport float64_t, intp_t
###############################################################################
# An object to be used in Python
# Lookup is faster than dict (up to 10 times), and so is full traversal
# (up to 50 times), and assignment (up to 6 times), but creation is
# slower (up to 3 times). Also, a large benefit is that memory
# consumption is reduced a lot compared to a Python dict
cdef class IntFloatDict:
def __init__(
self,
intp_t[:] keys,
float64_t[:] values,
):
cdef int i
cdef int size = values.size
# Should check that sizes for keys and values are equal, and
# after should boundcheck(False)
for i in range(size):
self.my_map[keys[i]] = values[i]
def __len__(self):
return self.my_map.size()
def __getitem__(self, int key):
cdef cpp_map[intp_t, float64_t].iterator it = self.my_map.find(key)
if it == self.my_map.end():
# The key is not in the dict
raise KeyError('%i' % key)
return deref(it).second
def __setitem__(self, int key, float value):
self.my_map[key] = value
# Cython 0.20 generates buggy code below. Commenting this out for now
# and relying on the to_arrays method
# def __iter__(self):
# cdef cpp_map[intp_t, float64_t].iterator it = self.my_map.begin()
# cdef cpp_map[intp_t, float64_t].iterator end = self.my_map.end()
# while it != end:
# yield deref(it).first, deref(it).second
# inc(it)
def __iter__(self):
cdef int size = self.my_map.size()
cdef intp_t [:] keys = np.empty(size, dtype=np.intp)
cdef float64_t [:] values = np.empty(size, dtype=np.float64)
self._to_arrays(keys, values)
cdef int idx
cdef intp_t key
cdef float64_t value
for idx in range(size):
key = keys[idx]
value = values[idx]
yield key, value
def to_arrays(self):
"""Return the key, value representation of the IntFloatDict
object.
Returns
=======
keys : ndarray, shape (n_items, ), dtype=int
The indices of the data points
values : ndarray, shape (n_items, ), dtype=float
The values of the data points
"""
cdef int size = self.my_map.size()
keys = np.empty(size, dtype=np.intp)
values = np.empty(size, dtype=np.float64)
self._to_arrays(keys, values)
return keys, values
cdef _to_arrays(self, intp_t [:] keys, float64_t [:] values):
# Internal version of to_arrays that takes already-initialized arrays
cdef cpp_map[intp_t, float64_t].iterator it = self.my_map.begin()
cdef cpp_map[intp_t, float64_t].iterator end = self.my_map.end()
cdef int index = 0
while it != end:
keys[index] = deref(it).first
values[index] = deref(it).second
inc(it)
index += 1
def update(self, IntFloatDict other):
cdef cpp_map[intp_t, float64_t].iterator it = other.my_map.begin()
cdef cpp_map[intp_t, float64_t].iterator end = other.my_map.end()
while it != end:
self.my_map[deref(it).first] = deref(it).second
inc(it)
def copy(self):
cdef IntFloatDict out_obj = IntFloatDict.__new__(IntFloatDict)
# The '=' operator is a copy operator for C++ maps
out_obj.my_map = self.my_map
return out_obj
def append(self, intp_t key, float64_t value):
# Construct our arguments
cdef pair[intp_t, float64_t] args
args.first = key
args.second = value
self.my_map.insert(args)
###############################################################################
# operation on dict
def argmin(IntFloatDict d):
cdef cpp_map[intp_t, float64_t].iterator it = d.my_map.begin()
cdef cpp_map[intp_t, float64_t].iterator end = d.my_map.end()
cdef intp_t min_key = -1
cdef float64_t min_value = np.inf
while it != end:
if deref(it).second < min_value:
min_value = deref(it).second
min_key = deref(it).first
inc(it)
return min_key, min_value
@@ -0,0 +1,14 @@
# Heap routines, used in various Cython implementations.
from cython cimport floating
from ._typedefs cimport intp_t
cdef int heap_push(
floating* values,
intp_t* indices,
intp_t size,
floating val,
intp_t val_idx,
) noexcept nogil
@@ -0,0 +1,85 @@
from cython cimport floating
from ._typedefs cimport intp_t
cdef inline int heap_push(
floating* values,
intp_t* indices,
intp_t size,
floating val,
intp_t val_idx,
) noexcept nogil:
"""Push a tuple (val, val_idx) onto a fixed-size max-heap.
The max-heap is represented as a Structure of Arrays where:
- values is the array containing the data to construct the heap with
- indices is the array containing the indices (meta-data) of each value
Notes
-----
Arrays are manipulated via a pointer to there first element and their size
as to ease the processing of dynamically allocated buffers.
For instance, in pseudo-code:
values = [1.2, 0.4, 0.1],
indices = [42, 1, 5],
heap_push(
values=values,
indices=indices,
size=3,
val=0.2,
val_idx=4,
)
will modify values and indices inplace, giving at the end of the call:
values == [0.4, 0.2, 0.1]
indices == [1, 4, 5]
"""
cdef:
intp_t current_idx, left_child_idx, right_child_idx, swap_idx
# Check if val should be in heap
if val >= values[0]:
return 0
# Insert val at position zero
values[0] = val
indices[0] = val_idx
# Descend the heap, swapping values until the max heap criterion is met
current_idx = 0
while True:
left_child_idx = 2 * current_idx + 1
right_child_idx = left_child_idx + 1
if left_child_idx >= size:
break
elif right_child_idx >= size:
if values[left_child_idx] > val:
swap_idx = left_child_idx
else:
break
elif values[left_child_idx] >= values[right_child_idx]:
if val < values[left_child_idx]:
swap_idx = left_child_idx
else:
break
else:
if val < values[right_child_idx]:
swap_idx = right_child_idx
else:
break
values[current_idx] = values[swap_idx]
indices[current_idx] = indices[swap_idx]
current_idx = swap_idx
values[current_idx] = val
indices[current_idx] = val_idx
return 0
@@ -0,0 +1,51 @@
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
from libc.math cimport isnan, isinf
from cython cimport floating
cpdef enum FiniteStatus:
all_finite = 0
has_nan = 1
has_infinite = 2
def cy_isfinite(floating[::1] a, bint allow_nan=False):
cdef FiniteStatus result
with nogil:
result = _isfinite(a, allow_nan)
return result
cdef inline FiniteStatus _isfinite(floating[::1] a, bint allow_nan) noexcept nogil:
cdef floating* a_ptr = &a[0]
cdef Py_ssize_t length = len(a)
if allow_nan:
return _isfinite_allow_nan(a_ptr, length)
else:
return _isfinite_disable_nan(a_ptr, length)
cdef inline FiniteStatus _isfinite_allow_nan(floating* a_ptr,
Py_ssize_t length) noexcept nogil:
cdef Py_ssize_t i
cdef floating v
for i in range(length):
v = a_ptr[i]
if isinf(v):
return FiniteStatus.has_infinite
return FiniteStatus.all_finite
cdef inline FiniteStatus _isfinite_disable_nan(floating* a_ptr,
Py_ssize_t length) noexcept nogil:
cdef Py_ssize_t i
cdef floating v
for i in range(length):
v = a_ptr[i]
if isnan(v):
return FiniteStatus.has_nan
elif isinf(v):
return FiniteStatus.has_infinite
return FiniteStatus.all_finite
@@ -0,0 +1,33 @@
# Helpers to safely access OpenMP routines
#
# no-op implementations are provided for the case where OpenMP is not available.
#
# All calls to OpenMP routines should be cimported from this module.
cdef extern from *:
"""
#ifdef _OPENMP
#include <omp.h>
#define SKLEARN_OPENMP_PARALLELISM_ENABLED 1
#else
#define SKLEARN_OPENMP_PARALLELISM_ENABLED 0
#define omp_lock_t int
#define omp_init_lock(l) (void)0
#define omp_destroy_lock(l) (void)0
#define omp_set_lock(l) (void)0
#define omp_unset_lock(l) (void)0
#define omp_get_thread_num() 0
#define omp_get_max_threads() 1
#endif
"""
bint SKLEARN_OPENMP_PARALLELISM_ENABLED
ctypedef struct omp_lock_t:
pass
void omp_init_lock(omp_lock_t*) noexcept nogil
void omp_destroy_lock(omp_lock_t*) noexcept nogil
void omp_set_lock(omp_lock_t*) noexcept nogil
void omp_unset_lock(omp_lock_t*) noexcept nogil
int omp_get_thread_num() noexcept nogil
int omp_get_max_threads() noexcept nogil
@@ -0,0 +1,77 @@
import os
from joblib import cpu_count
# Module level cache for cpu_count as we do not expect this to change during
# the lifecycle of a Python program. This dictionary is keyed by
# only_physical_cores.
_CPU_COUNTS = {}
def _openmp_parallelism_enabled():
"""Determines whether scikit-learn has been built with OpenMP
It allows to retrieve at runtime the information gathered at compile time.
"""
# SKLEARN_OPENMP_PARALLELISM_ENABLED is resolved at compile time and defined
# in _openmp_helpers.pxd as a boolean. This function exposes it to Python.
return SKLEARN_OPENMP_PARALLELISM_ENABLED
cpdef _openmp_effective_n_threads(n_threads=None, only_physical_cores=True):
"""Determine the effective number of threads to be used for OpenMP calls
- For ``n_threads = None``,
- if the ``OMP_NUM_THREADS`` environment variable is set, return
``openmp.omp_get_max_threads()``
- otherwise, return the minimum between ``openmp.omp_get_max_threads()``
and the number of cpus, taking cgroups quotas into account. Cgroups
quotas can typically be set by tools such as Docker.
The result of ``omp_get_max_threads`` can be influenced by environment
variable ``OMP_NUM_THREADS`` or at runtime by ``omp_set_num_threads``.
- For ``n_threads > 0``, return this as the maximal number of threads for
parallel OpenMP calls.
- For ``n_threads < 0``, return the maximal number of threads minus
``|n_threads + 1|``. In particular ``n_threads = -1`` will use as many
threads as there are available cores on the machine.
- Raise a ValueError for ``n_threads = 0``.
Passing the `only_physical_cores=False` flag makes it possible to use extra
threads for SMT/HyperThreading logical cores. It has been empirically
observed that using as many threads as available SMT cores can slightly
improve the performance in some cases, but can severely degrade
performance other times. Therefore it is recommended to use
`only_physical_cores=True` unless an empirical study has been conducted to
assess the impact of SMT on a case-by-case basis (using various input data
shapes, in particular small data shapes).
If scikit-learn is built without OpenMP support, always return 1.
"""
if n_threads == 0:
raise ValueError("n_threads = 0 is invalid")
if not SKLEARN_OPENMP_PARALLELISM_ENABLED:
# OpenMP disabled at build-time => sequential mode
return 1
if os.getenv("OMP_NUM_THREADS"):
# Fall back to user provided number of threads making it possible
# to exceed the number of cpus.
max_n_threads = omp_get_max_threads()
else:
try:
n_cpus = _CPU_COUNTS[only_physical_cores]
except KeyError:
n_cpus = cpu_count(only_physical_cores=only_physical_cores)
_CPU_COUNTS[only_physical_cores] = n_cpus
max_n_threads = min(omp_get_max_threads(), n_cpus)
if n_threads is None:
return max_n_threads
elif n_threads < 0:
return max(1, max_n_threads + n_threads + 1)
return n_threads
@@ -0,0 +1,34 @@
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
from ._typedefs cimport uint32_t
cdef inline uint32_t DEFAULT_SEED = 1
cdef enum:
# Max value for our rand_r replacement (near the bottom).
# We don't use RAND_MAX because it's different across platforms and
# particularly tiny on Windows/MSVC.
# It corresponds to the maximum representable value for
# 32-bit signed integers (i.e. 2^31 - 1).
RAND_R_MAX = 2147483647
# rand_r replacement using a 32bit XorShift generator
# See http://www.jstatsoft.org/v08/i14/paper for details
cdef inline uint32_t our_rand_r(uint32_t* seed) nogil:
"""Generate a pseudo-random np.uint32 from a np.uint32 seed"""
# seed shouldn't ever be 0.
if (seed[0] == 0):
seed[0] = DEFAULT_SEED
seed[0] ^= <uint32_t>(seed[0] << 13)
seed[0] ^= <uint32_t>(seed[0] >> 17)
seed[0] ^= <uint32_t>(seed[0] << 5)
# Use the modulo to make sure that we don't return a values greater than the
# maximum representable value for signed 32bit integers (i.e. 2^31 - 1).
# Note that the parenthesis are needed to avoid overflow: here
# RAND_R_MAX is cast to uint32_t before 1 is added.
return seed[0] % ((<uint32_t>RAND_R_MAX) + 1)
@@ -0,0 +1,355 @@
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
"""
Random utility function
=======================
This module complements missing features of ``numpy.random``.
The module contains:
* Several algorithms to sample integers without replacement.
* Fast rand_r alternative based on xor shifts
"""
import numpy as np
from . import check_random_state
from ._typedefs cimport intp_t
cdef uint32_t DEFAULT_SEED = 1
# Compatibility type to always accept the default int type used by NumPy, both
# before and after NumPy 2. On Windows, `long` does not always match `inp_t`.
# See the comments in the `sample_without_replacement` Python function for more
# details.
ctypedef fused default_int:
intp_t
long
cpdef _sample_without_replacement_check_input(default_int n_population,
default_int n_samples):
""" Check that input are consistent for sample_without_replacement"""
if n_population < 0:
raise ValueError('n_population should be greater than 0, got %s.'
% n_population)
if n_samples > n_population:
raise ValueError('n_population should be greater or equal than '
'n_samples, got n_samples > n_population (%s > %s)'
% (n_samples, n_population))
cpdef _sample_without_replacement_with_tracking_selection(
default_int n_population,
default_int n_samples,
random_state=None):
r"""Sample integers without replacement.
Select n_samples integers from the set [0, n_population) without
replacement.
Time complexity:
- Worst-case: unbounded
- Average-case:
O(O(np.random.randint) * \sum_{i=1}^n_samples 1 /
(1 - i / n_population)))
<= O(O(np.random.randint) *
n_population * ln((n_population - 2)
/(n_population - 1 - n_samples)))
<= O(O(np.random.randint) *
n_population * 1 / (1 - n_samples / n_population))
Space complexity of O(n_samples) in a python set.
Parameters
----------
n_population : int
The size of the set to sample from.
n_samples : int
The number of integer to sample.
random_state : int, RandomState instance or None, default=None
If int, random_state is the seed used by the random number generator;
If RandomState instance, random_state is the random number generator;
If None, the random number generator is the RandomState instance used
by `np.random`.
Returns
-------
out : ndarray of shape (n_samples,)
The sampled subsets of integer.
"""
_sample_without_replacement_check_input(n_population, n_samples)
cdef default_int i
cdef default_int j
cdef default_int[::1] out = np.empty((n_samples, ), dtype=int)
rng = check_random_state(random_state)
rng_randint = rng.randint
# The following line of code are heavily inspired from python core,
# more precisely of random.sample.
cdef set selected = set()
for i in range(n_samples):
j = rng_randint(n_population)
while j in selected:
j = rng_randint(n_population)
selected.add(j)
out[i] = j
return np.asarray(out)
cpdef _sample_without_replacement_with_pool(default_int n_population,
default_int n_samples,
random_state=None):
"""Sample integers without replacement.
Select n_samples integers from the set [0, n_population) without
replacement.
Time complexity: O(n_population + O(np.random.randint) * n_samples)
Space complexity of O(n_population + n_samples).
Parameters
----------
n_population : int
The size of the set to sample from.
n_samples : int
The number of integer to sample.
random_state : int, RandomState instance or None, default=None
If int, random_state is the seed used by the random number generator;
If RandomState instance, random_state is the random number generator;
If None, the random number generator is the RandomState instance used
by `np.random`.
Returns
-------
out : ndarray of shape (n_samples,)
The sampled subsets of integer.
"""
_sample_without_replacement_check_input(n_population, n_samples)
cdef default_int i
cdef default_int j
cdef default_int[::1] out = np.empty((n_samples,), dtype=int)
cdef default_int[::1] pool = np.empty((n_population,), dtype=int)
rng = check_random_state(random_state)
rng_randint = rng.randint
# Initialize the pool
for i in range(n_population):
pool[i] = i
# The following line of code are heavily inspired from python core,
# more precisely of random.sample.
for i in range(n_samples):
j = rng_randint(n_population - i) # invariant: non-selected at [0,n-i)
out[i] = pool[j]
pool[j] = pool[n_population - i - 1] # move non-selected item into vacancy
return np.asarray(out)
cpdef _sample_without_replacement_with_reservoir_sampling(
default_int n_population,
default_int n_samples,
random_state=None
):
"""Sample integers without replacement.
Select n_samples integers from the set [0, n_population) without
replacement.
Time complexity of
O((n_population - n_samples) * O(np.random.randint) + n_samples)
Space complexity of O(n_samples)
Parameters
----------
n_population : int
The size of the set to sample from.
n_samples : int
The number of integer to sample.
random_state : int, RandomState instance or None, default=None
If int, random_state is the seed used by the random number generator;
If RandomState instance, random_state is the random number generator;
If None, the random number generator is the RandomState instance used
by `np.random`.
Returns
-------
out : ndarray of shape (n_samples,)
The sampled subsets of integer. The order of the items is not
necessarily random. Use a random permutation of the array if the order
of the items has to be randomized.
"""
_sample_without_replacement_check_input(n_population, n_samples)
cdef default_int i
cdef default_int j
cdef default_int[::1] out = np.empty((n_samples, ), dtype=int)
rng = check_random_state(random_state)
rng_randint = rng.randint
# This cython implementation is based on the one of Robert Kern:
# http://mail.scipy.org/pipermail/numpy-discussion/2010-December/
# 054289.html
#
for i in range(n_samples):
out[i] = i
for i from n_samples <= i < n_population:
j = rng_randint(0, i + 1)
if j < n_samples:
out[j] = i
return np.asarray(out)
cdef _sample_without_replacement(default_int n_population,
default_int n_samples,
method="auto",
random_state=None):
"""Sample integers without replacement.
Private function for the implementation, see sample_without_replacement
documentation for more details.
"""
_sample_without_replacement_check_input(n_population, n_samples)
all_methods = ("auto", "tracking_selection", "reservoir_sampling", "pool")
ratio = <double> n_samples / n_population if n_population != 0.0 else 1.0
# Check ratio and use permutation unless ratio < 0.01 or ratio > 0.99
if method == "auto" and ratio > 0.01 and ratio < 0.99:
rng = check_random_state(random_state)
return rng.permutation(n_population)[:n_samples]
if method == "auto" or method == "tracking_selection":
# TODO the pool based method can also be used.
# however, it requires special benchmark to take into account
# the memory requirement of the array vs the set.
# The value 0.2 has been determined through benchmarking.
if ratio < 0.2:
return _sample_without_replacement_with_tracking_selection(
n_population, n_samples, random_state)
else:
return _sample_without_replacement_with_reservoir_sampling(
n_population, n_samples, random_state)
elif method == "reservoir_sampling":
return _sample_without_replacement_with_reservoir_sampling(
n_population, n_samples, random_state)
elif method == "pool":
return _sample_without_replacement_with_pool(n_population, n_samples,
random_state)
else:
raise ValueError('Expected a method name in %s, got %s. '
% (all_methods, method))
def sample_without_replacement(
object n_population, object n_samples, method="auto", random_state=None):
"""Sample integers without replacement.
Select n_samples integers from the set [0, n_population) without
replacement.
Parameters
----------
n_population : int
The size of the set to sample from.
n_samples : int
The number of integer to sample.
random_state : int, RandomState instance or None, default=None
If int, random_state is the seed used by the random number generator;
If RandomState instance, random_state is the random number generator;
If None, the random number generator is the RandomState instance used
by `np.random`.
method : {"auto", "tracking_selection", "reservoir_sampling", "pool"}, \
default='auto'
If method == "auto", the ratio of n_samples / n_population is used
to determine which algorithm to use:
If ratio is between 0 and 0.01, tracking selection is used.
If ratio is between 0.01 and 0.99, numpy.random.permutation is used.
If ratio is greater than 0.99, reservoir sampling is used.
The order of the selected integers is undefined. If a random order is
desired, the selected subset should be shuffled.
If method =="tracking_selection", a set based implementation is used
which is suitable for `n_samples` <<< `n_population`.
If method == "reservoir_sampling", a reservoir sampling algorithm is
used which is suitable for high memory constraint or when
O(`n_samples`) ~ O(`n_population`).
The order of the selected integers is undefined. If a random order is
desired, the selected subset should be shuffled.
If method == "pool", a pool based algorithm is particularly fast, even
faster than the tracking selection method. However, a vector containing
the entire population has to be initialized.
If n_samples ~ n_population, the reservoir sampling method is faster.
Returns
-------
out : ndarray of shape (n_samples,)
The sampled subsets of integer. The subset of selected integer might
not be randomized, see the method argument.
Examples
--------
>>> from sklearn.utils.random import sample_without_replacement
>>> sample_without_replacement(10, 5, random_state=42)
array([8, 1, 5, 0, 7])
"""
cdef:
intp_t n_pop_intp, n_samples_intp
long n_pop_long, n_samples_long
# On most platforms `np.int_ is np.intp`. However, before NumPy 2 the
# default integer `np.int_` was a long which is 32bit on 64bit windows
# while `intp` is 64bit on 64bit platforms and 32bit on 32bit ones.
if np.int_ is np.intp:
# Branch always taken on NumPy >=2 (or when not on 64bit windows).
# Cython has different rules for conversion of values to integers.
# For NumPy <1.26.2 AND Cython 3, this first branch requires `int()`
# called explicitly to allow e.g. floats.
n_pop_intp = int(n_population)
n_samples_intp = int(n_samples)
return _sample_without_replacement(
n_pop_intp, n_samples_intp, method, random_state)
else:
# Branch taken on 64bit windows with Numpy<2.0 where `long` is 32bit
n_pop_long = n_population
n_samples_long = n_samples
return _sample_without_replacement(
n_pop_long, n_samples_long, method, random_state)
def _our_rand_r_py(seed):
"""Python utils to test the our_rand_r function"""
cdef uint32_t my_seed = seed
return our_rand_r(&my_seed)
@@ -0,0 +1,413 @@
#$id {
/* Definition of color scheme common for light and dark mode */
--sklearn-color-text: #000;
--sklearn-color-text-muted: #666;
--sklearn-color-line: gray;
/* Definition of color scheme for unfitted estimators */
--sklearn-color-unfitted-level-0: #fff5e6;
--sklearn-color-unfitted-level-1: #f6e4d2;
--sklearn-color-unfitted-level-2: #ffe0b3;
--sklearn-color-unfitted-level-3: chocolate;
/* Definition of color scheme for fitted estimators */
--sklearn-color-fitted-level-0: #f0f8ff;
--sklearn-color-fitted-level-1: #d4ebff;
--sklearn-color-fitted-level-2: #b3dbfd;
--sklearn-color-fitted-level-3: cornflowerblue;
/* Specific color for light theme */
--sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));
--sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, white)));
--sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));
--sklearn-color-icon: #696969;
@media (prefers-color-scheme: dark) {
/* Redefinition of color scheme for dark theme */
--sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));
--sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, #111)));
--sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));
--sklearn-color-icon: #878787;
}
}
#$id {
color: var(--sklearn-color-text);
}
#$id pre {
padding: 0;
}
#$id input.sk-hidden--visually {
border: 0;
clip: rect(1px 1px 1px 1px);
clip: rect(1px, 1px, 1px, 1px);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
#$id div.sk-dashed-wrapped {
border: 1px dashed var(--sklearn-color-line);
margin: 0 0.4em 0.5em 0.4em;
box-sizing: border-box;
padding-bottom: 0.4em;
background-color: var(--sklearn-color-background);
}
#$id div.sk-container {
/* jupyter's `normalize.less` sets `[hidden] { display: none; }`
but bootstrap.min.css set `[hidden] { display: none !important; }`
so we also need the `!important` here to be able to override the
default hidden behavior on the sphinx rendered scikit-learn.org.
See: https://github.com/scikit-learn/scikit-learn/issues/21755 */
display: inline-block !important;
position: relative;
}
#$id div.sk-text-repr-fallback {
display: none;
}
div.sk-parallel-item,
div.sk-serial,
div.sk-item {
/* draw centered vertical line to link estimators */
background-image: linear-gradient(var(--sklearn-color-text-on-default-background), var(--sklearn-color-text-on-default-background));
background-size: 2px 100%;
background-repeat: no-repeat;
background-position: center center;
}
/* Parallel-specific style estimator block */
#$id div.sk-parallel-item::after {
content: "";
width: 100%;
border-bottom: 2px solid var(--sklearn-color-text-on-default-background);
flex-grow: 1;
}
#$id div.sk-parallel {
display: flex;
align-items: stretch;
justify-content: center;
background-color: var(--sklearn-color-background);
position: relative;
}
#$id div.sk-parallel-item {
display: flex;
flex-direction: column;
}
#$id div.sk-parallel-item:first-child::after {
align-self: flex-end;
width: 50%;
}
#$id div.sk-parallel-item:last-child::after {
align-self: flex-start;
width: 50%;
}
#$id div.sk-parallel-item:only-child::after {
width: 0;
}
/* Serial-specific style estimator block */
#$id div.sk-serial {
display: flex;
flex-direction: column;
align-items: center;
background-color: var(--sklearn-color-background);
padding-right: 1em;
padding-left: 1em;
}
/* Toggleable style: style used for estimator/Pipeline/ColumnTransformer box that is
clickable and can be expanded/collapsed.
- Pipeline and ColumnTransformer use this feature and define the default style
- Estimators will overwrite some part of the style using the `sk-estimator` class
*/
/* Pipeline and ColumnTransformer style (default) */
#$id div.sk-toggleable {
/* Default theme specific background. It is overwritten whether we have a
specific estimator or a Pipeline/ColumnTransformer */
background-color: var(--sklearn-color-background);
}
/* Toggleable label */
#$id label.sk-toggleable__label {
cursor: pointer;
display: flex;
width: 100%;
margin-bottom: 0;
padding: 0.5em;
box-sizing: border-box;
text-align: center;
align-items: start;
justify-content: space-between;
gap: 0.5em;
}
#$id label.sk-toggleable__label .caption {
font-size: 0.6rem;
font-weight: lighter;
color: var(--sklearn-color-text-muted);
}
#$id label.sk-toggleable__label-arrow:before {
/* Arrow on the left of the label */
content: "▸";
float: left;
margin-right: 0.25em;
color: var(--sklearn-color-icon);
}
#$id label.sk-toggleable__label-arrow:hover:before {
color: var(--sklearn-color-text);
}
/* Toggleable content - dropdown */
#$id div.sk-toggleable__content {
display: none;
text-align: left;
/* unfitted */
background-color: var(--sklearn-color-unfitted-level-0);
}
#$id div.sk-toggleable__content.fitted {
/* fitted */
background-color: var(--sklearn-color-fitted-level-0);
}
#$id div.sk-toggleable__content pre {
margin: 0.2em;
border-radius: 0.25em;
color: var(--sklearn-color-text);
/* unfitted */
background-color: var(--sklearn-color-unfitted-level-0);
}
#$id div.sk-toggleable__content.fitted pre {
/* unfitted */
background-color: var(--sklearn-color-fitted-level-0);
}
#$id input.sk-toggleable__control:checked~div.sk-toggleable__content {
/* Expand drop-down */
display: block;
width: 100%;
overflow: visible;
}
#$id input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {
content: "▾";
}
/* Pipeline/ColumnTransformer-specific style */
#$id div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {
color: var(--sklearn-color-text);
background-color: var(--sklearn-color-unfitted-level-2);
}
#$id div.sk-label.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {
background-color: var(--sklearn-color-fitted-level-2);
}
/* Estimator-specific style */
/* Colorize estimator box */
#$id div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {
/* unfitted */
background-color: var(--sklearn-color-unfitted-level-2);
}
#$id div.sk-estimator.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {
/* fitted */
background-color: var(--sklearn-color-fitted-level-2);
}
#$id div.sk-label label.sk-toggleable__label,
#$id div.sk-label label {
/* The background is the default theme color */
color: var(--sklearn-color-text-on-default-background);
}
/* On hover, darken the color of the background */
#$id div.sk-label:hover label.sk-toggleable__label {
color: var(--sklearn-color-text);
background-color: var(--sklearn-color-unfitted-level-2);
}
/* Label box, darken color on hover, fitted */
#$id div.sk-label.fitted:hover label.sk-toggleable__label.fitted {
color: var(--sklearn-color-text);
background-color: var(--sklearn-color-fitted-level-2);
}
/* Estimator label */
#$id div.sk-label label {
font-family: monospace;
font-weight: bold;
display: inline-block;
line-height: 1.2em;
}
#$id div.sk-label-container {
text-align: center;
}
/* Estimator-specific */
#$id div.sk-estimator {
font-family: monospace;
border: 1px dotted var(--sklearn-color-border-box);
border-radius: 0.25em;
box-sizing: border-box;
margin-bottom: 0.5em;
/* unfitted */
background-color: var(--sklearn-color-unfitted-level-0);
}
#$id div.sk-estimator.fitted {
/* fitted */
background-color: var(--sklearn-color-fitted-level-0);
}
/* on hover */
#$id div.sk-estimator:hover {
/* unfitted */
background-color: var(--sklearn-color-unfitted-level-2);
}
#$id div.sk-estimator.fitted:hover {
/* fitted */
background-color: var(--sklearn-color-fitted-level-2);
}
/* Specification for estimator info (e.g. "i" and "?") */
/* Common style for "i" and "?" */
.sk-estimator-doc-link,
a:link.sk-estimator-doc-link,
a:visited.sk-estimator-doc-link {
float: right;
font-size: smaller;
line-height: 1em;
font-family: monospace;
background-color: var(--sklearn-color-background);
border-radius: 1em;
height: 1em;
width: 1em;
text-decoration: none !important;
margin-left: 0.5em;
text-align: center;
/* unfitted */
border: var(--sklearn-color-unfitted-level-1) 1pt solid;
color: var(--sklearn-color-unfitted-level-1);
}
.sk-estimator-doc-link.fitted,
a:link.sk-estimator-doc-link.fitted,
a:visited.sk-estimator-doc-link.fitted {
/* fitted */
border: var(--sklearn-color-fitted-level-1) 1pt solid;
color: var(--sklearn-color-fitted-level-1);
}
/* On hover */
div.sk-estimator:hover .sk-estimator-doc-link:hover,
.sk-estimator-doc-link:hover,
div.sk-label-container:hover .sk-estimator-doc-link:hover,
.sk-estimator-doc-link:hover {
/* unfitted */
background-color: var(--sklearn-color-unfitted-level-3);
color: var(--sklearn-color-background);
text-decoration: none;
}
div.sk-estimator.fitted:hover .sk-estimator-doc-link.fitted:hover,
.sk-estimator-doc-link.fitted:hover,
div.sk-label-container:hover .sk-estimator-doc-link.fitted:hover,
.sk-estimator-doc-link.fitted:hover {
/* fitted */
background-color: var(--sklearn-color-fitted-level-3);
color: var(--sklearn-color-background);
text-decoration: none;
}
/* Span, style for the box shown on hovering the info icon */
.sk-estimator-doc-link span {
display: none;
z-index: 9999;
position: relative;
font-weight: normal;
right: .2ex;
padding: .5ex;
margin: .5ex;
width: min-content;
min-width: 20ex;
max-width: 50ex;
color: var(--sklearn-color-text);
box-shadow: 2pt 2pt 4pt #999;
/* unfitted */
background: var(--sklearn-color-unfitted-level-0);
border: .5pt solid var(--sklearn-color-unfitted-level-3);
}
.sk-estimator-doc-link.fitted span {
/* fitted */
background: var(--sklearn-color-fitted-level-0);
border: var(--sklearn-color-fitted-level-3);
}
.sk-estimator-doc-link:hover span {
display: block;
}
/* "?"-specific style due to the `<a>` HTML tag */
#$id a.estimator_doc_link {
float: right;
font-size: 1rem;
line-height: 1em;
font-family: monospace;
background-color: var(--sklearn-color-background);
border-radius: 1rem;
height: 1rem;
width: 1rem;
text-decoration: none;
/* unfitted */
color: var(--sklearn-color-unfitted-level-1);
border: var(--sklearn-color-unfitted-level-1) 1pt solid;
}
#$id a.estimator_doc_link.fitted {
/* fitted */
border: var(--sklearn-color-fitted-level-1) 1pt solid;
color: var(--sklearn-color-fitted-level-1);
}
/* On hover */
#$id a.estimator_doc_link:hover {
/* unfitted */
background-color: var(--sklearn-color-unfitted-level-3);
color: var(--sklearn-color-background);
text-decoration: none;
}
#$id a.estimator_doc_link.fitted:hover {
/* fitted */
background-color: var(--sklearn-color-fitted-level-3);
}
@@ -0,0 +1,42 @@
function copyToClipboard(text, element) {
// Get the parameter prefix from the closest toggleable content
const toggleableContent = element.closest('.sk-toggleable__content');
const paramPrefix = toggleableContent ? toggleableContent.dataset.paramPrefix : '';
const fullParamName = paramPrefix ? `${paramPrefix}${text}` : text;
const originalStyle = element.style;
const computedStyle = window.getComputedStyle(element);
const originalWidth = computedStyle.width;
const originalHTML = element.innerHTML.replace('Copied!', '');
navigator.clipboard.writeText(fullParamName)
.then(() => {
element.style.width = originalWidth;
element.style.color = 'green';
element.innerHTML = "Copied!";
setTimeout(() => {
element.innerHTML = originalHTML;
element.style = originalStyle;
}, 2000);
})
.catch(err => {
console.error('Failed to copy:', err);
element.style.color = 'red';
element.innerHTML = "Failed!";
setTimeout(() => {
element.innerHTML = originalHTML;
element.style = originalStyle;
}, 2000);
});
return false;
}
document.querySelectorAll('.fa-regular.fa-copy').forEach(function(element) {
const toggleableContent = element.closest('.sk-toggleable__content');
const paramPrefix = toggleableContent ? toggleableContent.dataset.paramPrefix : '';
const paramName = element.parentElement.nextElementSibling.textContent.trim();
const fullParamName = paramPrefix ? `${paramPrefix}${paramName}` : paramName;
element.setAttribute('title', fullParamName);
});
@@ -0,0 +1,63 @@
.estimator-table summary {
padding: .5rem;
font-family: monospace;
cursor: pointer;
}
.estimator-table details[open] {
padding-left: 0.1rem;
padding-right: 0.1rem;
padding-bottom: 0.3rem;
}
.estimator-table .parameters-table {
margin-left: auto !important;
margin-right: auto !important;
}
.estimator-table .parameters-table tr:nth-child(odd) {
background-color: #fff;
}
.estimator-table .parameters-table tr:nth-child(even) {
background-color: #f6f6f6;
}
.estimator-table .parameters-table tr:hover {
background-color: #e0e0e0;
}
.estimator-table table td {
border: 1px solid rgba(106, 105, 104, 0.232);
}
.user-set td {
color:rgb(255, 94, 0);
text-align: left;
}
.user-set td.value pre {
color:rgb(255, 94, 0) !important;
background-color: transparent !important;
}
.default td {
color: black;
text-align: left;
}
.user-set td i,
.default td i {
color: black;
}
.copy-paste-icon {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA0NDggNTEyIj48IS0tIUZvbnQgQXdlc29tZSBGcmVlIDYuNy4yIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlL2ZyZWUgQ29weXJpZ2h0IDIwMjUgRm9udGljb25zLCBJbmMuLS0+PHBhdGggZD0iTTIwOCAwTDMzMi4xIDBjMTIuNyAwIDI0LjkgNS4xIDMzLjkgMTQuMWw2Ny45IDY3LjljOSA5IDE0LjEgMjEuMiAxNC4xIDMzLjlMNDQ4IDMzNmMwIDI2LjUtMjEuNSA0OC00OCA0OGwtMTkyIDBjLTI2LjUgMC00OC0yMS41LTQ4LTQ4bDAtMjg4YzAtMjYuNSAyMS41LTQ4IDQ4LTQ4ek00OCAxMjhsODAgMCAwIDY0LTY0IDAgMCAyNTYgMTkyIDAgMC0zMiA2NCAwIDAgNDhjMCAyNi41LTIxLjUgNDgtNDggNDhMNDggNTEyYy0yNi41IDAtNDgtMjEuNS00OC00OEwwIDE3NmMwLTI2LjUgMjEuNS00OCA0OC00OHoiLz48L3N2Zz4=);
background-repeat: no-repeat;
background-size: 14px 14px;
background-position: 0;
display: inline-block;
width: 14px;
height: 14px;
cursor: pointer;
}
@@ -0,0 +1,76 @@
{{py:
"""
Dataset abstractions for sequential data access.
Template file for easily generate fused types consistent code using Tempita
(https://github.com/cython/cython/blob/master/Cython/Tempita/_tempita.py).
Generated file: _seq_dataset.pxd
Each class is duplicated for all dtypes (float and double). The keywords
between double braces are substituted during the build.
"""
# name_suffix, c_type
dtypes = [('64', 'float64_t'),
('32', 'float32_t')]
}}
"""Dataset abstractions for sequential data access."""
from ._typedefs cimport float32_t, float64_t, intp_t, uint32_t
# SequentialDataset and its two concrete subclasses are (optionally randomized)
# iterators over the rows of a matrix X and corresponding target values y.
{{for name_suffix, c_type in dtypes}}
#------------------------------------------------------------------------------
cdef class SequentialDataset{{name_suffix}}:
cdef int current_index
cdef int[::1] index
cdef int *index_data_ptr
cdef Py_ssize_t n_samples
cdef uint32_t seed
cdef void shuffle(self, uint32_t seed) noexcept nogil
cdef int _get_next_index(self) noexcept nogil
cdef int _get_random_index(self) noexcept nogil
cdef void _sample(self, {{c_type}} **x_data_ptr, int **x_ind_ptr,
int *nnz, {{c_type}} *y, {{c_type}} *sample_weight,
int current_index) noexcept nogil
cdef void next(self, {{c_type}} **x_data_ptr, int **x_ind_ptr,
int *nnz, {{c_type}} *y, {{c_type}} *sample_weight) noexcept nogil
cdef int random(self, {{c_type}} **x_data_ptr, int **x_ind_ptr,
int *nnz, {{c_type}} *y, {{c_type}} *sample_weight) noexcept nogil
cdef class ArrayDataset{{name_suffix}}(SequentialDataset{{name_suffix}}):
cdef const {{c_type}}[:, ::1] X
cdef const {{c_type}}[::1] Y
cdef const {{c_type}}[::1] sample_weights
cdef Py_ssize_t n_features
cdef intp_t X_stride
cdef {{c_type}} *X_data_ptr
cdef {{c_type}} *Y_data_ptr
cdef const int[::1] feature_indices
cdef int *feature_indices_ptr
cdef {{c_type}} *sample_weight_data
cdef class CSRDataset{{name_suffix}}(SequentialDataset{{name_suffix}}):
cdef const {{c_type}}[::1] X_data
cdef const int[::1] X_indptr
cdef const int[::1] X_indices
cdef const {{c_type}}[::1] Y
cdef const {{c_type}}[::1] sample_weights
cdef {{c_type}} *X_data_ptr
cdef int *X_indptr_ptr
cdef int *X_indices_ptr
cdef {{c_type}} *Y_data_ptr
cdef {{c_type}} *sample_weight_data
{{endfor}}
@@ -0,0 +1,348 @@
{{py:
"""
Dataset abstractions for sequential data access.
Template file for easily generate fused types consistent code using Tempita
(https://github.com/cython/cython/blob/master/Cython/Tempita/_tempita.py).
Generated file: _seq_dataset.pyx
Each class is duplicated for all dtypes (float and double). The keywords
between double braces are substituted during the build.
"""
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
# name_suffix, c_type, np_type
dtypes = [('64', 'float64_t', 'np.float64'),
('32', 'float32_t', 'np.float32')]
}}
"""Dataset abstractions for sequential data access."""
import numpy as np
cimport cython
from libc.limits cimport INT_MAX
from ._random cimport our_rand_r
from ._typedefs cimport float32_t, float64_t, uint32_t
{{for name_suffix, c_type, np_type in dtypes}}
#------------------------------------------------------------------------------
cdef class SequentialDataset{{name_suffix}}:
"""Base class for datasets with sequential data access.
SequentialDataset is used to iterate over the rows of a matrix X and
corresponding target values y, i.e. to iterate over samples.
There are two methods to get the next sample:
- next : Iterate sequentially (optionally randomized)
- random : Iterate randomly (with replacement)
Attributes
----------
index : np.ndarray
Index array for fast shuffling.
index_data_ptr : int
Pointer to the index array.
current_index : int
Index of current sample in ``index``.
The index of current sample in the data is given by
index_data_ptr[current_index].
n_samples : Py_ssize_t
Number of samples in the dataset.
seed : uint32_t
Seed used for random sampling. This attribute is modified at each call to the
`random` method.
"""
cdef void next(self, {{c_type}} **x_data_ptr, int **x_ind_ptr,
int *nnz, {{c_type}} *y, {{c_type}} *sample_weight) noexcept nogil:
"""Get the next example ``x`` from the dataset.
This method gets the next sample looping sequentially over all samples.
The order can be shuffled with the method ``shuffle``.
Shuffling once before iterating over all samples corresponds to a
random draw without replacement. It is used for instance in SGD solver.
Parameters
----------
x_data_ptr : {{c_type}}**
A pointer to the {{c_type}} array which holds the feature
values of the next example.
x_ind_ptr : np.intc**
A pointer to the int array which holds the feature
indices of the next example.
nnz : int*
A pointer to an int holding the number of non-zero
values of the next example.
y : {{c_type}}*
The target value of the next example.
sample_weight : {{c_type}}*
The weight of the next example.
"""
cdef int current_index = self._get_next_index()
self._sample(x_data_ptr, x_ind_ptr, nnz, y, sample_weight,
current_index)
cdef int random(self, {{c_type}} **x_data_ptr, int **x_ind_ptr,
int *nnz, {{c_type}} *y, {{c_type}} *sample_weight) noexcept nogil:
"""Get a random example ``x`` from the dataset.
This method gets next sample chosen randomly over a uniform
distribution. It corresponds to a random draw with replacement.
It is used for instance in SAG solver.
Parameters
----------
x_data_ptr : {{c_type}}**
A pointer to the {{c_type}} array which holds the feature
values of the next example.
x_ind_ptr : np.intc**
A pointer to the int array which holds the feature
indices of the next example.
nnz : int*
A pointer to an int holding the number of non-zero
values of the next example.
y : {{c_type}}*
The target value of the next example.
sample_weight : {{c_type}}*
The weight of the next example.
Returns
-------
current_index : int
Index of current sample.
"""
cdef int current_index = self._get_random_index()
self._sample(x_data_ptr, x_ind_ptr, nnz, y, sample_weight,
current_index)
return current_index
cdef void shuffle(self, uint32_t seed) noexcept nogil:
"""Permutes the ordering of examples."""
# Fisher-Yates shuffle
cdef int *ind = self.index_data_ptr
cdef int n = self.n_samples
cdef unsigned i, j
for i in range(n - 1):
j = i + our_rand_r(&seed) % (n - i)
ind[i], ind[j] = ind[j], ind[i]
cdef int _get_next_index(self) noexcept nogil:
cdef int current_index = self.current_index
if current_index >= (self.n_samples - 1):
current_index = -1
current_index += 1
self.current_index = current_index
return self.current_index
cdef int _get_random_index(self) noexcept nogil:
cdef int n = self.n_samples
cdef int current_index = our_rand_r(&self.seed) % n
self.current_index = current_index
return current_index
cdef void _sample(self, {{c_type}} **x_data_ptr, int **x_ind_ptr,
int *nnz, {{c_type}} *y, {{c_type}} *sample_weight,
int current_index) noexcept nogil:
pass
def _shuffle_py(self, uint32_t seed):
"""python function used for easy testing"""
self.shuffle(seed)
def _next_py(self):
"""python function used for easy testing"""
cdef int current_index = self._get_next_index()
return self._sample_py(current_index)
def _random_py(self):
"""python function used for easy testing"""
cdef int current_index = self._get_random_index()
return self._sample_py(current_index)
def _sample_py(self, int current_index):
"""python function used for easy testing"""
cdef {{c_type}}* x_data_ptr
cdef int* x_indices_ptr
cdef int nnz, j
cdef {{c_type}} y, sample_weight
# call _sample in cython
self._sample(&x_data_ptr, &x_indices_ptr, &nnz, &y, &sample_weight,
current_index)
# transform the pointed data in numpy CSR array
cdef {{c_type}}[:] x_data = np.empty(nnz, dtype={{np_type}})
cdef int[:] x_indices = np.empty(nnz, dtype=np.int32)
cdef int[:] x_indptr = np.asarray([0, nnz], dtype=np.int32)
for j in range(nnz):
x_data[j] = x_data_ptr[j]
x_indices[j] = x_indices_ptr[j]
cdef int sample_idx = self.index_data_ptr[current_index]
return (
(np.asarray(x_data), np.asarray(x_indices), np.asarray(x_indptr)),
y,
sample_weight,
sample_idx,
)
cdef class ArrayDataset{{name_suffix}}(SequentialDataset{{name_suffix}}):
"""Dataset backed by a two-dimensional numpy array.
The dtype of the numpy array is expected to be ``{{np_type}}`` ({{c_type}})
and C-style memory layout.
"""
def __cinit__(
self,
const {{c_type}}[:, ::1] X,
const {{c_type}}[::1] Y,
const {{c_type}}[::1] sample_weights,
uint32_t seed=1,
):
"""A ``SequentialDataset`` backed by a two-dimensional numpy array.
Parameters
----------
X : ndarray, dtype={{c_type}}, ndim=2, mode='c'
The sample array, of shape(n_samples, n_features)
Y : ndarray, dtype={{c_type}}, ndim=1, mode='c'
The target array, of shape(n_samples, )
sample_weights : ndarray, dtype={{c_type}}, ndim=1, mode='c'
The weight of each sample, of shape(n_samples,)
"""
if X.shape[0] > INT_MAX or X.shape[1] > INT_MAX:
raise ValueError("More than %d samples or features not supported;"
" got (%d, %d)."
% (INT_MAX, X.shape[0], X.shape[1]))
# keep a reference to the data to prevent garbage collection
self.X = X
self.Y = Y
self.sample_weights = sample_weights
self.n_samples = X.shape[0]
self.n_features = X.shape[1]
self.feature_indices = np.arange(0, self.n_features, dtype=np.intc)
self.feature_indices_ptr = <int *> &self.feature_indices[0]
self.current_index = -1
self.X_stride = X.strides[0] // X.itemsize
self.X_data_ptr = <{{c_type}} *> &X[0, 0]
self.Y_data_ptr = <{{c_type}} *> &Y[0]
self.sample_weight_data = <{{c_type}} *> &sample_weights[0]
# Use index array for fast shuffling
self.index = np.arange(0, self.n_samples, dtype=np.intc)
self.index_data_ptr = <int *> &self.index[0]
# seed should not be 0 for our_rand_r
self.seed = max(seed, 1)
cdef void _sample(self, {{c_type}} **x_data_ptr, int **x_ind_ptr,
int *nnz, {{c_type}} *y, {{c_type}} *sample_weight,
int current_index) noexcept nogil:
cdef long long sample_idx = self.index_data_ptr[current_index]
cdef long long offset = sample_idx * self.X_stride
y[0] = self.Y_data_ptr[sample_idx]
x_data_ptr[0] = self.X_data_ptr + offset
x_ind_ptr[0] = self.feature_indices_ptr
nnz[0] = self.n_features
sample_weight[0] = self.sample_weight_data[sample_idx]
cdef class CSRDataset{{name_suffix}}(SequentialDataset{{name_suffix}}):
"""A ``SequentialDataset`` backed by a scipy sparse CSR matrix. """
def __cinit__(
self,
const {{c_type}}[::1] X_data,
const int[::1] X_indptr,
const int[::1] X_indices,
const {{c_type}}[::1] Y,
const {{c_type}}[::1] sample_weights,
uint32_t seed=1,
):
"""Dataset backed by a scipy sparse CSR matrix.
The feature indices of ``x`` are given by x_ind_ptr[0:nnz].
The corresponding feature values are given by
x_data_ptr[0:nnz].
Parameters
----------
X_data : ndarray, dtype={{c_type}}, ndim=1, mode='c'
The data array of the CSR features matrix.
X_indptr : ndarray, dtype=np.intc, ndim=1, mode='c'
The index pointer array of the CSR features matrix.
X_indices : ndarray, dtype=np.intc, ndim=1, mode='c'
The column indices array of the CSR features matrix.
Y : ndarray, dtype={{c_type}}, ndim=1, mode='c'
The target values.
sample_weights : ndarray, dtype={{c_type}}, ndim=1, mode='c'
The weight of each sample.
"""
# keep a reference to the data to prevent garbage collection
self.X_data = X_data
self.X_indptr = X_indptr
self.X_indices = X_indices
self.Y = Y
self.sample_weights = sample_weights
self.n_samples = Y.shape[0]
self.current_index = -1
self.X_data_ptr = <{{c_type}} *> &X_data[0]
self.X_indptr_ptr = <int *> &X_indptr[0]
self.X_indices_ptr = <int *> &X_indices[0]
self.Y_data_ptr = <{{c_type}} *> &Y[0]
self.sample_weight_data = <{{c_type}} *> &sample_weights[0]
# Use index array for fast shuffling
self.index = np.arange(self.n_samples, dtype=np.intc)
self.index_data_ptr = <int *> &self.index[0]
# seed should not be 0 for our_rand_r
self.seed = max(seed, 1)
cdef void _sample(self, {{c_type}} **x_data_ptr, int **x_ind_ptr,
int *nnz, {{c_type}} *y, {{c_type}} *sample_weight,
int current_index) noexcept nogil:
cdef long long sample_idx = self.index_data_ptr[current_index]
cdef long long offset = self.X_indptr_ptr[sample_idx]
y[0] = self.Y_data_ptr[sample_idx]
x_data_ptr[0] = self.X_data_ptr + offset
x_ind_ptr[0] = self.X_indices_ptr + offset
nnz[0] = self.X_indptr_ptr[sample_idx + 1] - offset
sample_weight[0] = self.sample_weight_data[sample_idx]
{{endfor}}
@@ -0,0 +1,9 @@
from ._typedefs cimport intp_t
from cython cimport floating
cdef int simultaneous_sort(
floating *dist,
intp_t *idx,
intp_t size,
) noexcept nogil
@@ -0,0 +1,93 @@
from cython cimport floating
cdef inline void dual_swap(
floating* darr,
intp_t *iarr,
intp_t a,
intp_t b,
) noexcept nogil:
"""Swap the values at index a and b of both darr and iarr"""
cdef floating dtmp = darr[a]
darr[a] = darr[b]
darr[b] = dtmp
cdef intp_t itmp = iarr[a]
iarr[a] = iarr[b]
iarr[b] = itmp
cdef int simultaneous_sort(
floating* values,
intp_t* indices,
intp_t size,
) noexcept nogil:
"""
Perform a recursive quicksort on the values array as to sort them ascendingly.
This simultaneously performs the swaps on both the values and the indices arrays.
The numpy equivalent is:
def simultaneous_sort(dist, idx):
i = np.argsort(dist)
return dist[i], idx[i]
Notes
-----
Arrays are manipulated via a pointer to there first element and their size
as to ease the processing of dynamically allocated buffers.
"""
# TODO: In order to support discrete distance metrics, we need to have a
# simultaneous sort which breaks ties on indices when distances are identical.
# The best might be using a std::stable_sort and a Comparator which might need
# an Array of Structures (AoS) instead of the Structure of Arrays (SoA)
# currently used.
cdef:
intp_t pivot_idx, i, store_idx
floating pivot_val
# in the small-array case, do things efficiently
if size <= 1:
pass
elif size == 2:
if values[0] > values[1]:
dual_swap(values, indices, 0, 1)
elif size == 3:
if values[0] > values[1]:
dual_swap(values, indices, 0, 1)
if values[1] > values[2]:
dual_swap(values, indices, 1, 2)
if values[0] > values[1]:
dual_swap(values, indices, 0, 1)
else:
# Determine the pivot using the median-of-three rule.
# The smallest of the three is moved to the beginning of the array,
# the middle (the pivot value) is moved to the end, and the largest
# is moved to the pivot index.
pivot_idx = size // 2
if values[0] > values[size - 1]:
dual_swap(values, indices, 0, size - 1)
if values[size - 1] > values[pivot_idx]:
dual_swap(values, indices, size - 1, pivot_idx)
if values[0] > values[size - 1]:
dual_swap(values, indices, 0, size - 1)
pivot_val = values[size - 1]
# Partition indices about pivot. At the end of this operation,
# pivot_idx will contain the pivot value, everything to the left
# will be smaller, and everything to the right will be larger.
store_idx = 0
for i in range(size - 1):
if values[i] < pivot_val:
dual_swap(values, indices, i, store_idx)
store_idx += 1
dual_swap(values, indices, store_idx, size - 1)
pivot_idx = store_idx
# Recursively sort each side of the pivot
if pivot_idx > 1:
simultaneous_sort(values, indices, pivot_idx)
if pivot_idx + 2 < size:
simultaneous_sort(values + pivot_idx + 1,
indices + pivot_idx + 1,
size - pivot_idx - 1)
return 0
@@ -0,0 +1,41 @@
# Commonly used types
# These are redefinitions of the ones defined by numpy in
# https://github.com/numpy/numpy/blob/main/numpy/__init__.pxd.
# It will eventually avoid having to always include the numpy headers even when we
# would only use it for the types.
#
# When used to declare variables that will receive values from numpy arrays, it
# should match the dtype of the array. For example, to declare a variable that will
# receive values from a numpy array of dtype np.float64, the type float64_t must be
# used.
#
# TODO: Stop defining custom types locally or globally like DTYPE_t and friends and
# use these consistently throughout the codebase.
# NOTE: Extend this list as needed when converting more cython extensions.
ctypedef unsigned char uint8_t
ctypedef unsigned int uint32_t
ctypedef unsigned long long uint64_t
# Note: In NumPy 2, indexing always happens with npy_intp which is an alias for
# the Py_ssize_t type, see PEP 353.
#
# Note that on most platforms Py_ssize_t is equivalent to C99's intptr_t,
# but they can differ on architecture with segmented memory (none
# supported by scikit-learn at the time of writing).
#
# intp_t/np.intp should be used to index arrays in a platform dependent way.
# Storing arrays with platform dependent dtypes as attribute on picklable
# objects is not recommended as it requires special care when loading and
# using such datastructures on a host with different bitness. Instead one
# should rather use fixed width integer types such as int32 or uint32 when we know
# that the number of elements to index is not larger to 2 or 4 billions.
ctypedef Py_ssize_t intp_t
ctypedef float float32_t
ctypedef double float64_t
# Sparse matrices indices and indices' pointers arrays must use int32_t over
# intp_t because intp_t is platform dependent.
# When large sparse matrices are supported, indexing must use int64_t.
# See https://github.com/scikit-learn/scikit-learn/issues/23653 which tracks the
# ongoing work to support large sparse matrices.
ctypedef signed char int8_t
ctypedef signed int int32_t
ctypedef signed long long int64_t
@@ -0,0 +1,23 @@
# _typedefs is a declaration only module
#
# The functions implemented here are for testing purpose only.
import numpy as np
ctypedef fused testing_type_t:
float32_t
float64_t
int8_t
int32_t
int64_t
intp_t
uint8_t
uint32_t
uint64_t
def testing_make_array_from_typed_val(testing_type_t val):
cdef testing_type_t[:] val_view = <testing_type_t[:1]>&val
return np.asarray(val_view)
@@ -0,0 +1,12 @@
cimport numpy as cnp
from libcpp.vector cimport vector
from ..utils._typedefs cimport intp_t, float64_t, int32_t, int64_t
ctypedef fused vector_typed:
vector[float64_t]
vector[intp_t]
vector[int32_t]
vector[int64_t]
cdef cnp.ndarray vector_to_nd_array(vector_typed * vect_ptr)
@@ -0,0 +1,118 @@
from cython.operator cimport dereference as deref
from cpython.ref cimport Py_INCREF
cimport numpy as cnp
cnp.import_array()
cdef StdVectorSentinel _create_sentinel(vector_typed * vect_ptr):
if vector_typed is vector[float64_t]:
return StdVectorSentinelFloat64.create_for(vect_ptr)
elif vector_typed is vector[int32_t]:
return StdVectorSentinelInt32.create_for(vect_ptr)
elif vector_typed is vector[int64_t]:
return StdVectorSentinelInt64.create_for(vect_ptr)
else: # intp_t
return StdVectorSentinelIntP.create_for(vect_ptr)
cdef class StdVectorSentinel:
"""Wraps a reference to a vector which will be deallocated with this object.
When created, the StdVectorSentinel swaps the reference of its internal
vectors with the provided one (vect_ptr), thus making the StdVectorSentinel
manage the provided one's lifetime.
"""
cdef void* get_data(self):
"""Return pointer to data."""
cdef int get_typenum(self):
"""Get typenum for PyArray_SimpleNewFromData."""
cdef class StdVectorSentinelFloat64(StdVectorSentinel):
cdef vector[float64_t] vec
@staticmethod
cdef StdVectorSentinel create_for(vector[float64_t] * vect_ptr):
# This initializes the object directly without calling __init__
# See: https://cython.readthedocs.io/en/latest/src/userguide/extension_types.html#instantiation-from-existing-c-c-pointers # noqa
cdef StdVectorSentinelFloat64 sentinel = StdVectorSentinelFloat64.__new__(StdVectorSentinelFloat64)
sentinel.vec.swap(deref(vect_ptr))
return sentinel
cdef void* get_data(self):
return self.vec.data()
cdef int get_typenum(self):
return cnp.NPY_FLOAT64
cdef class StdVectorSentinelIntP(StdVectorSentinel):
cdef vector[intp_t] vec
@staticmethod
cdef StdVectorSentinel create_for(vector[intp_t] * vect_ptr):
# This initializes the object directly without calling __init__
# See: https://cython.readthedocs.io/en/latest/src/userguide/extension_types.html#instantiation-from-existing-c-c-pointers # noqa
cdef StdVectorSentinelIntP sentinel = StdVectorSentinelIntP.__new__(StdVectorSentinelIntP)
sentinel.vec.swap(deref(vect_ptr))
return sentinel
cdef void* get_data(self):
return self.vec.data()
cdef int get_typenum(self):
return cnp.NPY_INTP
cdef class StdVectorSentinelInt32(StdVectorSentinel):
cdef vector[int32_t] vec
@staticmethod
cdef StdVectorSentinel create_for(vector[int32_t] * vect_ptr):
# This initializes the object directly without calling __init__
# See: https://cython.readthedocs.io/en/latest/src/userguide/extension_types.html#instantiation-from-existing-c-c-pointers # noqa
cdef StdVectorSentinelInt32 sentinel = StdVectorSentinelInt32.__new__(StdVectorSentinelInt32)
sentinel.vec.swap(deref(vect_ptr))
return sentinel
cdef void* get_data(self):
return self.vec.data()
cdef int get_typenum(self):
return cnp.NPY_INT32
cdef class StdVectorSentinelInt64(StdVectorSentinel):
cdef vector[int64_t] vec
@staticmethod
cdef StdVectorSentinel create_for(vector[int64_t] * vect_ptr):
# This initializes the object directly without calling __init__
# See: https://cython.readthedocs.io/en/latest/src/userguide/extension_types.html#instantiation-from-existing-c-c-pointers # noqa
cdef StdVectorSentinelInt64 sentinel = StdVectorSentinelInt64.__new__(StdVectorSentinelInt64)
sentinel.vec.swap(deref(vect_ptr))
return sentinel
cdef void* get_data(self):
return self.vec.data()
cdef int get_typenum(self):
return cnp.NPY_INT64
cdef cnp.ndarray vector_to_nd_array(vector_typed * vect_ptr):
cdef:
cnp.npy_intp size = deref(vect_ptr).size()
StdVectorSentinel sentinel = _create_sentinel(vect_ptr)
cnp.ndarray arr = cnp.PyArray_SimpleNewFromData(
1, &size, sentinel.get_typenum(), sentinel.get_data())
# Makes the numpy array responsible of the life-cycle of its buffer.
# A reference to the StdVectorSentinel will be stolen by the call to
# `PyArray_SetBaseObject` below, so we increase its reference counter.
# See: https://docs.python.org/3/c-api/intro.html#reference-count-details
Py_INCREF(sentinel)
cnp.PyArray_SetBaseObject(arr, sentinel)
return arr
@@ -0,0 +1,45 @@
{{py:
"""
Efficient (dense) parameter vector implementation for linear models.
Template file for easily generate fused types consistent code using Tempita
(https://github.com/cython/cython/blob/master/Cython/Tempita/_tempita.py).
Generated file: weight_vector.pxd
Each class is duplicated for all dtypes (float and double). The keywords
between double braces are substituted during the build.
"""
# name_suffix, c_type
dtypes = [('64', 'double'),
('32', 'float')]
}}
{{for name_suffix, c_type in dtypes}}
cdef class WeightVector{{name_suffix}}(object):
cdef readonly {{c_type}}[::1] w
cdef readonly {{c_type}}[::1] aw
cdef {{c_type}} *w_data_ptr
cdef {{c_type}} *aw_data_ptr
cdef double wscale
cdef double average_a
cdef double average_b
cdef int n_features
cdef double sq_norm
cdef void add(self, {{c_type}} *x_data_ptr, int *x_ind_ptr,
int xnnz, {{c_type}} c) noexcept nogil
cdef void add_average(self, {{c_type}} *x_data_ptr, int *x_ind_ptr,
int xnnz, {{c_type}} c, {{c_type}} num_iter) noexcept nogil
cdef {{c_type}} dot(self, {{c_type}} *x_data_ptr, int *x_ind_ptr,
int xnnz) noexcept nogil
cdef void scale(self, {{c_type}} c) noexcept nogil
cdef void reset_wscale(self) noexcept nogil
cdef {{c_type}} norm(self) noexcept nogil
{{endfor}}
@@ -0,0 +1,207 @@
{{py:
"""
Efficient (dense) parameter vector implementation for linear models.
Template file for easily generate fused types consistent code using Tempita
(https://github.com/cython/cython/blob/master/Cython/Tempita/_tempita.py).
Generated file: weight_vector.pxd
Each class is duplicated for all dtypes (float and double). The keywords
between double braces are substituted during the build.
"""
# name_suffix, c_type, reset_wscale_threshold
dtypes = [('64', 'double', 1e-9),
('32', 'float', 1e-6)]
}}
# cython: binding=False
#
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
cimport cython
from libc.limits cimport INT_MAX
from libc.math cimport sqrt
from ._cython_blas cimport _dot, _scal, _axpy
{{for name_suffix, c_type, reset_wscale_threshold in dtypes}}
cdef class WeightVector{{name_suffix}}(object):
"""Dense vector represented by a scalar and a numpy array.
The class provides methods to ``add`` a sparse vector
and scale the vector.
Representing a vector explicitly as a scalar times a
vector allows for efficient scaling operations.
Attributes
----------
w : ndarray, dtype={{c_type}}, order='C'
The numpy array which backs the weight vector.
aw : ndarray, dtype={{c_type}}, order='C'
The numpy array which backs the average_weight vector.
w_data_ptr : {{c_type}}*
A pointer to the data of the numpy array.
wscale : {{c_type}}
The scale of the vector.
n_features : int
The number of features (= dimensionality of ``w``).
sq_norm : {{c_type}}
The squared norm of ``w``.
"""
def __cinit__(self,
{{c_type}}[::1] w,
{{c_type}}[::1] aw):
if w.shape[0] > INT_MAX:
raise ValueError("More than %d features not supported; got %d."
% (INT_MAX, w.shape[0]))
self.w = w
self.w_data_ptr = &w[0]
self.wscale = 1.0
self.n_features = w.shape[0]
self.sq_norm = _dot(self.n_features, self.w_data_ptr, 1, self.w_data_ptr, 1)
self.aw = aw
if self.aw is not None:
self.aw_data_ptr = &aw[0]
self.average_a = 0.0
self.average_b = 1.0
cdef void add(self, {{c_type}} *x_data_ptr, int *x_ind_ptr, int xnnz,
{{c_type}} c) noexcept nogil:
"""Scales sample x by constant c and adds it to the weight vector.
This operation updates ``sq_norm``.
Parameters
----------
x_data_ptr : {{c_type}}*
The array which holds the feature values of ``x``.
x_ind_ptr : np.intc*
The array which holds the feature indices of ``x``.
xnnz : int
The number of non-zero features of ``x``.
c : {{c_type}}
The scaling constant for the example.
"""
cdef int j
cdef int idx
cdef double val
cdef double innerprod = 0.0
cdef double xsqnorm = 0.0
# the next two lines save a factor of 2!
cdef {{c_type}} wscale = self.wscale
cdef {{c_type}}* w_data_ptr = self.w_data_ptr
for j in range(xnnz):
idx = x_ind_ptr[j]
val = x_data_ptr[j]
innerprod += (w_data_ptr[idx] * val)
xsqnorm += (val * val)
w_data_ptr[idx] += val * (c / wscale)
self.sq_norm += (xsqnorm * c * c) + (2.0 * innerprod * wscale * c)
# Update the average weights according to the sparse trick defined
# here: https://research.microsoft.com/pubs/192769/tricks-2012.pdf
# by Leon Bottou
cdef void add_average(self, {{c_type}} *x_data_ptr, int *x_ind_ptr, int xnnz,
{{c_type}} c, {{c_type}} num_iter) noexcept nogil:
"""Updates the average weight vector.
Parameters
----------
x_data_ptr : {{c_type}}*
The array which holds the feature values of ``x``.
x_ind_ptr : np.intc*
The array which holds the feature indices of ``x``.
xnnz : int
The number of non-zero features of ``x``.
c : {{c_type}}
The scaling constant for the example.
num_iter : {{c_type}}
The total number of iterations.
"""
cdef int j
cdef int idx
cdef double val
cdef double mu = 1.0 / num_iter
cdef double average_a = self.average_a
cdef double wscale = self.wscale
cdef {{c_type}}* aw_data_ptr = self.aw_data_ptr
for j in range(xnnz):
idx = x_ind_ptr[j]
val = x_data_ptr[j]
aw_data_ptr[idx] += (self.average_a * val * (-c / wscale))
# Once the sample has been processed
# update the average_a and average_b
if num_iter > 1:
self.average_b /= (1.0 - mu)
self.average_a += mu * self.average_b * wscale
cdef {{c_type}} dot(self, {{c_type}} *x_data_ptr, int *x_ind_ptr,
int xnnz) noexcept nogil:
"""Computes the dot product of a sample x and the weight vector.
Parameters
----------
x_data_ptr : {{c_type}}*
The array which holds the feature values of ``x``.
x_ind_ptr : np.intc*
The array which holds the feature indices of ``x``.
xnnz : int
The number of non-zero features of ``x`` (length of x_ind_ptr).
Returns
-------
innerprod : {{c_type}}
The inner product of ``x`` and ``w``.
"""
cdef int j
cdef int idx
cdef double innerprod = 0.0
cdef {{c_type}}* w_data_ptr = self.w_data_ptr
for j in range(xnnz):
idx = x_ind_ptr[j]
innerprod += w_data_ptr[idx] * x_data_ptr[j]
innerprod *= self.wscale
return innerprod
cdef void scale(self, {{c_type}} c) noexcept nogil:
"""Scales the weight vector by a constant ``c``.
It updates ``wscale`` and ``sq_norm``. If ``wscale`` gets too
small we call ``reset_swcale``."""
self.wscale *= c
self.sq_norm *= (c * c)
if self.wscale < {{reset_wscale_threshold}}:
self.reset_wscale()
cdef void reset_wscale(self) noexcept nogil:
"""Scales each coef of ``w`` by ``wscale`` and resets it to 1. """
if self.aw_data_ptr != NULL:
_axpy(self.n_features, self.average_a,
self.w_data_ptr, 1, self.aw_data_ptr, 1)
_scal(self.n_features, 1.0 / self.average_b, self.aw_data_ptr, 1)
self.average_a = 0.0
self.average_b = 1.0
_scal(self.n_features, self.wscale, self.w_data_ptr, 1)
self.wscale = 1.0
cdef {{c_type}} norm(self) noexcept nogil:
"""The L2 norm of the weight vector. """
return sqrt(self.sq_norm)
{{endfor}}
@@ -0,0 +1,118 @@
"""A small collection of auxiliary functions that operate on arrays."""
from cython cimport floating
from libc.math cimport fabs
from libc.float cimport DBL_MAX, FLT_MAX
from ._cython_blas cimport _copy, _rotg, _rot
ctypedef fused real_numeric:
short
int
long
long long
float
double
def min_pos(const floating[:] X):
"""Find the minimum value of an array over positive values.
Returns the maximum representable value of the input dtype if none of the
values are positive.
Parameters
----------
X : ndarray of shape (n,)
Input array.
Returns
-------
min_val : float
The smallest positive value in the array, or the maximum representable value
of the input dtype if no positive values are found.
Examples
--------
>>> import numpy as np
>>> from sklearn.utils.arrayfuncs import min_pos
>>> X = np.array([0, -1, 2, 3, -4, 5])
>>> min_pos(X)
2.0
"""
cdef Py_ssize_t i
cdef floating min_val = FLT_MAX if floating is float else DBL_MAX
for i in range(X.size):
if 0. < X[i] < min_val:
min_val = X[i]
return min_val
def _all_with_any_reduction_axis_1(real_numeric[:, :] array, real_numeric value):
"""Check whether any row contains all values equal to `value`.
It is equivalent to `np.any(np.all(X == value, axis=1))`, but it avoids to
materialize the temporary boolean matrices in memory.
Parameters
----------
array: array-like
The array to be checked.
value: short, int, long, float, or double
The value to use for the comparison.
Returns
-------
any_all_equal: bool
Whether or not any rows contains all values equal to `value`.
"""
cdef Py_ssize_t i, j
for i in range(array.shape[0]):
for j in range(array.shape[1]):
if array[i, j] != value:
break
else: # no break
return True
return False
# General Cholesky Delete.
# Remove an element from the cholesky factorization
# m = columns
# n = rows
#
# TODO: put transpose as an option
def cholesky_delete(floating[:, :] L, int go_out):
cdef:
int n = L.shape[0]
int m = L.strides[0]
floating c, s
floating *L1
int i
if floating is float:
m /= sizeof(float)
else:
m /= sizeof(double)
# delete row go_out
L1 = &L[0, 0] + (go_out * m)
for i in range(go_out, n-1):
_copy(i + 2, L1 + m, 1, L1, 1)
L1 += m
L1 = &L[0, 0] + (go_out * m)
for i in range(go_out, n-1):
_rotg(L1 + i, L1 + i + 1, &c, &s)
if L1[i] < 0:
# Diagonals cannot be negative
L1[i] = fabs(L1[i])
c = -c
s = -s
L1[i + 1] = 0. # just for cleanup
L1 += m
_rot(n - i - 2, L1 + i, m, L1 + i + 1, m, c, s)
@@ -0,0 +1,75 @@
# utils is cimported from other subpackages so this is needed for the cimport
# to work
utils_cython_tree = [
# We add sklearn_root_cython_tree to make sure sklearn/__init__.py is copied
# early in the build
sklearn_root_cython_tree,
fs.copyfile('__init__.py'),
fs.copyfile('_cython_blas.pxd'),
fs.copyfile('_heap.pxd'),
fs.copyfile('_openmp_helpers.pxd'),
fs.copyfile('_random.pxd'),
fs.copyfile('_sorting.pxd'),
fs.copyfile('_typedefs.pxd'),
fs.copyfile('_vector_sentinel.pxd'),
]
utils_extension_metadata = {
'sparsefuncs_fast':
{'sources': [cython_gen.process('sparsefuncs_fast.pyx')]},
'_cython_blas': {'sources': [cython_gen.process('_cython_blas.pyx')]},
'arrayfuncs': {'sources': [cython_gen.process('arrayfuncs.pyx')]},
'murmurhash': {
'sources': [cython_gen.process('murmurhash.pyx'), 'src' / 'MurmurHash3.cpp'],
},
'_fast_dict':
{'sources': [cython_gen_cpp.process('_fast_dict.pyx')]},
'_openmp_helpers': {'sources': [cython_gen.process('_openmp_helpers.pyx')], 'dependencies': [openmp_dep]},
'_random': {'sources': [cython_gen.process('_random.pyx')]},
'_typedefs': {'sources': [cython_gen.process('_typedefs.pyx')]},
'_heap': {'sources': [cython_gen.process('_heap.pyx')]},
'_sorting': {'sources': [cython_gen.process('_sorting.pyx')]},
'_vector_sentinel':
{'sources': [cython_gen_cpp.process('_vector_sentinel.pyx')],
'dependencies': [np_dep]},
'_isfinite': {'sources': [cython_gen.process('_isfinite.pyx')]},
}
foreach ext_name, ext_dict : utils_extension_metadata
py.extension_module(
ext_name,
[ext_dict.get('sources'), utils_cython_tree],
dependencies: ext_dict.get('dependencies', []),
subdir: 'sklearn/utils',
install: true
)
endforeach
util_extension_names = ['_seq_dataset', '_weight_vector']
foreach name: util_extension_names
pxd = custom_target(
name + '_pxd',
output: name + '.pxd',
input: name + '.pxd.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@'],
)
utils_cython_tree += [pxd]
pyx = custom_target(
name + '_pyx',
output: name + '.pyx',
input: name + '.pyx.tp',
command: [tempita, '@INPUT@', '-o', '@OUTDIR@'],
# TODO in principle this should go in py.exension_module below. This is
# temporary work-around for dependency issue with .pyx.tp files. For more
# details, see https://github.com/mesonbuild/meson/issues/13212
depends: [pxd, utils_cython_tree],
)
py.extension_module(
name,
cython_gen.process(pyx),
subdir: 'sklearn/utils',
install: true
)
endforeach
@@ -0,0 +1,21 @@
"""Export fast murmurhash C/C++ routines + cython wrappers"""
from ..utils._typedefs cimport int32_t, uint32_t
# The C API is disabled for now, since it requires -I flags to get
# compilation to work even when these functions are not used.
# cdef extern from "MurmurHash3.h":
# void MurmurHash3_x86_32(void* key, int len, unsigned int seed,
# void* out)
#
# void MurmurHash3_x86_128(void* key, int len, unsigned int seed,
# void* out)
#
# void MurmurHash3_x64_128(void* key, int len, unsigned int seed,
# void* out)
cpdef uint32_t murmurhash3_int_u32(int key, unsigned int seed)
cpdef int32_t murmurhash3_int_s32(int key, unsigned int seed)
cpdef uint32_t murmurhash3_bytes_u32(bytes key, unsigned int seed)
cpdef int32_t murmurhash3_bytes_s32(bytes key, unsigned int seed)
@@ -0,0 +1,135 @@
"""Cython wrapper for MurmurHash3 non-cryptographic hash function.
MurmurHash is an extensively tested and very fast hash function that has
good distribution properties suitable for machine learning use cases
such as feature hashing and random projections.
The original C++ code by Austin Appleby is released the public domain
and can be found here:
https://code.google.com/p/smhasher/
"""
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
from ..utils._typedefs cimport int32_t, uint32_t
import numpy as np
cdef extern from "src/MurmurHash3.h":
void MurmurHash3_x86_32(void *key, int len, uint32_t seed, void *out)
void MurmurHash3_x86_128(void *key, int len, uint32_t seed, void *out)
void MurmurHash3_x64_128 (void *key, int len, uint32_t seed, void *out)
cpdef uint32_t murmurhash3_int_u32(int key, unsigned int seed):
"""Compute the 32bit murmurhash3 of a int key at seed."""
cdef uint32_t out
MurmurHash3_x86_32(&key, sizeof(int), seed, &out)
return out
cpdef int32_t murmurhash3_int_s32(int key, unsigned int seed):
"""Compute the 32bit murmurhash3 of a int key at seed."""
cdef int32_t out
MurmurHash3_x86_32(&key, sizeof(int), seed, &out)
return out
cpdef uint32_t murmurhash3_bytes_u32(bytes key, unsigned int seed):
"""Compute the 32bit murmurhash3 of a bytes key at seed."""
cdef uint32_t out
MurmurHash3_x86_32(<char*> key, len(key), seed, &out)
return out
cpdef int32_t murmurhash3_bytes_s32(bytes key, unsigned int seed):
"""Compute the 32bit murmurhash3 of a bytes key at seed."""
cdef int32_t out
MurmurHash3_x86_32(<char*> key, len(key), seed, &out)
return out
def _murmurhash3_bytes_array_u32(
const int32_t[:] key,
unsigned int seed,
):
"""Compute 32bit murmurhash3 hashes of a key int array at seed."""
# TODO make it possible to pass preallocated output array
cdef:
uint32_t[:] out = np.zeros(key.size, np.uint32)
Py_ssize_t i
for i in range(key.shape[0]):
out[i] = murmurhash3_int_u32(key[i], seed)
return np.asarray(out)
def _murmurhash3_bytes_array_s32(
const int32_t[:] key,
unsigned int seed,
):
"""Compute 32bit murmurhash3 hashes of a key int array at seed."""
# TODO make it possible to pass preallocated output array
cdef:
int32_t[:] out = np.zeros(key.size, np.int32)
Py_ssize_t i
for i in range(key.shape[0]):
out[i] = murmurhash3_int_s32(key[i], seed)
return np.asarray(out)
def murmurhash3_32(key, seed=0, positive=False):
"""Compute the 32bit murmurhash3 of key at seed.
The underlying implementation is MurmurHash3_x86_32 generating low
latency 32bits hash suitable for implementing lookup tables, Bloom
filters, count min sketch or feature hashing.
Parameters
----------
key : np.int32, bytes, unicode or ndarray of dtype=np.int32
The physical object to hash.
seed : int, default=0
Integer seed for the hashing algorithm.
positive : bool, default=False
True: the results is casted to an unsigned int
from 0 to 2 ** 32 - 1
False: the results is casted to a signed int
from -(2 ** 31) to 2 ** 31 - 1
Examples
--------
>>> from sklearn.utils import murmurhash3_32
>>> murmurhash3_32(b"Hello World!", seed=42)
3565178
"""
if isinstance(key, bytes):
if positive:
return murmurhash3_bytes_u32(key, seed)
else:
return murmurhash3_bytes_s32(key, seed)
elif isinstance(key, unicode):
if positive:
return murmurhash3_bytes_u32(key.encode('utf-8'), seed)
else:
return murmurhash3_bytes_s32(key.encode('utf-8'), seed)
elif isinstance(key, int) or isinstance(key, np.int32):
if positive:
return murmurhash3_int_u32(<int32_t>key, seed)
else:
return murmurhash3_int_s32(<int32_t>key, seed)
elif isinstance(key, np.ndarray):
if key.dtype != np.int32:
raise TypeError(
"key.dtype should be int32, got %s" % key.dtype)
if positive:
return _murmurhash3_bytes_array_u32(key.ravel(), seed).reshape(key.shape)
else:
return _murmurhash3_bytes_array_s32(key.ravel(), seed).reshape(key.shape)
else:
raise TypeError(
"key %r with type %s is not supported. "
"Explicit conversion to bytes is required" % (key, type(key)))
@@ -0,0 +1,640 @@
"""Utilities to work with sparse matrices and arrays written in Cython."""
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
from libc.math cimport fabs, sqrt, isnan
from libc.stdint cimport intptr_t
import numpy as np
from cython cimport floating
from ..utils._typedefs cimport float64_t, int32_t, int64_t, intp_t, uint64_t
ctypedef fused integral:
int32_t
int64_t
def csr_row_norms(X):
"""Squared L2 norm of each row in CSR matrix X."""
if X.dtype not in [np.float32, np.float64]:
X = X.astype(np.float64)
return _sqeuclidean_row_norms_sparse(X.data, X.indptr)
def _sqeuclidean_row_norms_sparse(
const floating[::1] X_data,
const integral[::1] X_indptr,
):
cdef:
integral n_samples = X_indptr.shape[0] - 1
integral i, j
dtype = np.float32 if floating is float else np.float64
cdef floating[::1] squared_row_norms = np.zeros(n_samples, dtype=dtype)
with nogil:
for i in range(n_samples):
for j in range(X_indptr[i], X_indptr[i + 1]):
squared_row_norms[i] += X_data[j] * X_data[j]
return np.asarray(squared_row_norms)
def csr_mean_variance_axis0(X, weights=None, return_sum_weights=False):
"""Compute mean and variance along axis 0 on a CSR matrix
Uses a np.float64 accumulator.
Parameters
----------
X : CSR sparse matrix, shape (n_samples, n_features)
Input data.
weights : ndarray of shape (n_samples,), dtype=floating, default=None
If it is set to None samples will be equally weighted.
.. versionadded:: 0.24
return_sum_weights : bool, default=False
If True, returns the sum of weights seen for each feature.
.. versionadded:: 0.24
Returns
-------
means : float array with shape (n_features,)
Feature-wise means
variances : float array with shape (n_features,)
Feature-wise variances
sum_weights : ndarray of shape (n_features,), dtype=floating
Returned if return_sum_weights is True.
"""
if X.dtype not in [np.float32, np.float64]:
X = X.astype(np.float64)
if weights is None:
weights = np.ones(X.shape[0], dtype=X.dtype)
means, variances, sum_weights = _csr_mean_variance_axis0(
X.data, X.shape[0], X.shape[1], X.indices, X.indptr, weights)
if return_sum_weights:
return means, variances, sum_weights
return means, variances
def _csr_mean_variance_axis0(
const floating[::1] X_data,
uint64_t n_samples,
uint64_t n_features,
const integral[:] X_indices,
const integral[:] X_indptr,
const floating[:] weights,
):
# Implement the function here since variables using fused types
# cannot be declared directly and can only be passed as function arguments
cdef:
intp_t row_ind
uint64_t feature_idx
integral i, col_ind
float64_t diff
# means[j] contains the mean of feature j
float64_t[::1] means = np.zeros(n_features)
# variances[j] contains the variance of feature j
float64_t[::1] variances = np.zeros(n_features)
float64_t[::1] sum_weights = np.full(
fill_value=np.sum(weights, dtype=np.float64), shape=n_features
)
float64_t[::1] sum_weights_nz = np.zeros(shape=n_features)
float64_t[::1] correction = np.zeros(shape=n_features)
uint64_t[::1] counts = np.full(
fill_value=weights.shape[0], shape=n_features, dtype=np.uint64
)
uint64_t[::1] counts_nz = np.zeros(shape=n_features, dtype=np.uint64)
for row_ind in range(len(X_indptr) - 1):
for i in range(X_indptr[row_ind], X_indptr[row_ind + 1]):
col_ind = X_indices[i]
if not isnan(X_data[i]):
means[col_ind] += <float64_t>(X_data[i]) * weights[row_ind]
# sum of weights where X[:, col_ind] is non-zero
sum_weights_nz[col_ind] += weights[row_ind]
# number of non-zero elements of X[:, col_ind]
counts_nz[col_ind] += 1
else:
# sum of weights where X[:, col_ind] is not nan
sum_weights[col_ind] -= weights[row_ind]
# number of non nan elements of X[:, col_ind]
counts[col_ind] -= 1
for feature_idx in range(n_features):
means[feature_idx] /= sum_weights[feature_idx]
for row_ind in range(len(X_indptr) - 1):
for i in range(X_indptr[row_ind], X_indptr[row_ind + 1]):
col_ind = X_indices[i]
if not isnan(X_data[i]):
diff = X_data[i] - means[col_ind]
# correction term of the corrected 2 pass algorithm.
# See "Algorithms for computing the sample variance: analysis
# and recommendations", by Chan, Golub, and LeVeque.
correction[col_ind] += diff * weights[row_ind]
variances[col_ind] += diff * diff * weights[row_ind]
for feature_idx in range(n_features):
if counts[feature_idx] != counts_nz[feature_idx]:
correction[feature_idx] -= (
sum_weights[feature_idx] - sum_weights_nz[feature_idx]
) * means[feature_idx]
correction[feature_idx] = correction[feature_idx]**2 / sum_weights[feature_idx]
if counts[feature_idx] != counts_nz[feature_idx]:
# only compute it when it's guaranteed to be non-zero to avoid
# catastrophic cancellation.
variances[feature_idx] += (
sum_weights[feature_idx] - sum_weights_nz[feature_idx]
) * means[feature_idx]**2
variances[feature_idx] = (
(variances[feature_idx] - correction[feature_idx]) /
sum_weights[feature_idx]
)
if floating is float:
return (
np.array(means, dtype=np.float32),
np.array(variances, dtype=np.float32),
np.array(sum_weights, dtype=np.float32),
)
else:
return (
np.asarray(means), np.asarray(variances), np.asarray(sum_weights)
)
def csc_mean_variance_axis0(X, weights=None, return_sum_weights=False):
"""Compute mean and variance along axis 0 on a CSC matrix
Uses a np.float64 accumulator.
Parameters
----------
X : CSC sparse matrix, shape (n_samples, n_features)
Input data.
weights : ndarray of shape (n_samples,), dtype=floating, default=None
If it is set to None samples will be equally weighted.
.. versionadded:: 0.24
return_sum_weights : bool, default=False
If True, returns the sum of weights seen for each feature.
.. versionadded:: 0.24
Returns
-------
means : float array with shape (n_features,)
Feature-wise means
variances : float array with shape (n_features,)
Feature-wise variances
sum_weights : ndarray of shape (n_features,), dtype=floating
Returned if return_sum_weights is True.
"""
if X.dtype not in [np.float32, np.float64]:
X = X.astype(np.float64)
if weights is None:
weights = np.ones(X.shape[0], dtype=X.dtype)
means, variances, sum_weights = _csc_mean_variance_axis0(
X.data, X.shape[0], X.shape[1], X.indices, X.indptr, weights)
if return_sum_weights:
return means, variances, sum_weights
return means, variances
def _csc_mean_variance_axis0(
const floating[::1] X_data,
uint64_t n_samples,
uint64_t n_features,
const integral[:] X_indices,
const integral[:] X_indptr,
const floating[:] weights,
):
# Implement the function here since variables using fused types
# cannot be declared directly and can only be passed as function arguments
cdef:
integral i, row_ind
uint64_t feature_idx, col_ind
float64_t diff
# means[j] contains the mean of feature j
float64_t[::1] means = np.zeros(n_features)
# variances[j] contains the variance of feature j
float64_t[::1] variances = np.zeros(n_features)
float64_t[::1] sum_weights = np.full(
fill_value=np.sum(weights, dtype=np.float64), shape=n_features
)
float64_t[::1] sum_weights_nz = np.zeros(shape=n_features)
float64_t[::1] correction = np.zeros(shape=n_features)
uint64_t[::1] counts = np.full(
fill_value=weights.shape[0], shape=n_features, dtype=np.uint64
)
uint64_t[::1] counts_nz = np.zeros(shape=n_features, dtype=np.uint64)
for col_ind in range(n_features):
for i in range(X_indptr[col_ind], X_indptr[col_ind + 1]):
row_ind = X_indices[i]
if not isnan(X_data[i]):
means[col_ind] += <float64_t>(X_data[i]) * weights[row_ind]
# sum of weights where X[:, col_ind] is non-zero
sum_weights_nz[col_ind] += weights[row_ind]
# number of non-zero elements of X[:, col_ind]
counts_nz[col_ind] += 1
else:
# sum of weights where X[:, col_ind] is not nan
sum_weights[col_ind] -= weights[row_ind]
# number of non nan elements of X[:, col_ind]
counts[col_ind] -= 1
for feature_idx in range(n_features):
means[feature_idx] /= sum_weights[feature_idx]
for col_ind in range(n_features):
for i in range(X_indptr[col_ind], X_indptr[col_ind + 1]):
row_ind = X_indices[i]
if not isnan(X_data[i]):
diff = X_data[i] - means[col_ind]
# correction term of the corrected 2 pass algorithm.
# See "Algorithms for computing the sample variance: analysis
# and recommendations", by Chan, Golub, and LeVeque.
correction[col_ind] += diff * weights[row_ind]
variances[col_ind] += diff * diff * weights[row_ind]
for feature_idx in range(n_features):
if counts[feature_idx] != counts_nz[feature_idx]:
correction[feature_idx] -= (
sum_weights[feature_idx] - sum_weights_nz[feature_idx]
) * means[feature_idx]
correction[feature_idx] = correction[feature_idx]**2 / sum_weights[feature_idx]
if counts[feature_idx] != counts_nz[feature_idx]:
# only compute it when it's guaranteed to be non-zero to avoid
# catastrophic cancellation.
variances[feature_idx] += (
sum_weights[feature_idx] - sum_weights_nz[feature_idx]
) * means[feature_idx]**2
variances[feature_idx] = (
(variances[feature_idx] - correction[feature_idx])
) / sum_weights[feature_idx]
if floating is float:
return (np.array(means, dtype=np.float32),
np.array(variances, dtype=np.float32),
np.array(sum_weights, dtype=np.float32))
else:
return (
np.asarray(means), np.asarray(variances), np.asarray(sum_weights)
)
def incr_mean_variance_axis0(X, last_mean, last_var, last_n, weights=None):
"""Compute mean and variance along axis 0 on a CSR or CSC matrix.
last_mean, last_var are the statistics computed at the last step by this
function. Both must be initialized to 0.0. last_n is the
number of samples encountered until now and is initialized at 0.
Parameters
----------
X : CSR or CSC sparse matrix, shape (n_samples, n_features)
Input data.
last_mean : float array with shape (n_features,)
Array of feature-wise means to update with the new data X.
last_var : float array with shape (n_features,)
Array of feature-wise var to update with the new data X.
last_n : float array with shape (n_features,)
Sum of the weights seen so far (if weights are all set to 1
this will be the same as number of samples seen so far, before X).
weights : float array with shape (n_samples,) or None. If it is set
to None samples will be equally weighted.
Returns
-------
updated_mean : float array with shape (n_features,)
Feature-wise means
updated_variance : float array with shape (n_features,)
Feature-wise variances
updated_n : int array with shape (n_features,)
Updated number of samples seen
Notes
-----
NaNs are ignored during the computation.
References
----------
T. Chan, G. Golub, R. LeVeque. Algorithms for computing the sample
variance: recommendations, The American Statistician, Vol. 37, No. 3,
pp. 242-247
Also, see the non-sparse implementation of this in
`utils.extmath._batch_mean_variance_update`.
"""
if X.dtype not in [np.float32, np.float64]:
X = X.astype(np.float64)
X_dtype = X.dtype
if weights is None:
weights = np.ones(X.shape[0], dtype=X_dtype)
elif weights.dtype not in [np.float32, np.float64]:
weights = weights.astype(np.float64, copy=False)
if last_n.dtype not in [np.float32, np.float64]:
last_n = last_n.astype(np.float64, copy=False)
return _incr_mean_variance_axis0(X.data,
np.sum(weights),
X.shape[1],
X.indices,
X.indptr,
X.format,
last_mean.astype(X_dtype, copy=False),
last_var.astype(X_dtype, copy=False),
last_n.astype(X_dtype, copy=False),
weights.astype(X_dtype, copy=False))
def _incr_mean_variance_axis0(
const floating[:] X_data,
floating n_samples,
uint64_t n_features,
const int[:] X_indices,
# X_indptr might be either int32 or int64
const integral[:] X_indptr,
str X_format,
floating[:] last_mean,
floating[:] last_var,
floating[:] last_n,
# previous sum of the weights (ie float)
const floating[:] weights,
):
# Implement the function here since variables using fused types
# cannot be declared directly and can only be passed as function arguments
cdef:
uint64_t i
# last = stats until now
# new = the current increment
# updated = the aggregated stats
# when arrays, they are indexed by i per-feature
floating[::1] new_mean
floating[::1] new_var
floating[::1] updated_mean
floating[::1] updated_var
if floating is float:
dtype = np.float32
else:
dtype = np.float64
new_mean = np.zeros(n_features, dtype=dtype)
new_var = np.zeros_like(new_mean, dtype=dtype)
updated_mean = np.zeros_like(new_mean, dtype=dtype)
updated_var = np.zeros_like(new_mean, dtype=dtype)
cdef:
floating[::1] new_n
floating[::1] updated_n
floating[::1] last_over_new_n
# Obtain new stats first
updated_n = np.zeros(shape=n_features, dtype=dtype)
last_over_new_n = np.zeros_like(updated_n, dtype=dtype)
# X can be a CSR or CSC matrix
if X_format == 'csr':
new_mean, new_var, new_n = _csr_mean_variance_axis0(
X_data, n_samples, n_features, X_indices, X_indptr, weights)
else: # X_format == 'csc'
new_mean, new_var, new_n = _csc_mean_variance_axis0(
X_data, n_samples, n_features, X_indices, X_indptr, weights)
# First pass
cdef bint is_first_pass = True
for i in range(n_features):
if last_n[i] > 0:
is_first_pass = False
break
if is_first_pass:
return np.asarray(new_mean), np.asarray(new_var), np.asarray(new_n)
for i in range(n_features):
updated_n[i] = last_n[i] + new_n[i]
# Next passes
for i in range(n_features):
if new_n[i] > 0:
last_over_new_n[i] = dtype(last_n[i]) / dtype(new_n[i])
# Unnormalized stats
last_mean[i] *= last_n[i]
last_var[i] *= last_n[i]
new_mean[i] *= new_n[i]
new_var[i] *= new_n[i]
# Update stats
updated_var[i] = (
last_var[i] + new_var[i] +
last_over_new_n[i] / updated_n[i] *
(last_mean[i] / last_over_new_n[i] - new_mean[i])**2
)
updated_mean[i] = (last_mean[i] + new_mean[i]) / updated_n[i]
updated_var[i] /= updated_n[i]
else:
updated_var[i] = last_var[i]
updated_mean[i] = last_mean[i]
updated_n[i] = last_n[i]
return (
np.asarray(updated_mean),
np.asarray(updated_var),
np.asarray(updated_n),
)
def inplace_csr_row_normalize_l1(X):
"""Normalize inplace the rows of a CSR matrix or array by their L1 norm.
Parameters
----------
X : scipy.sparse.csr_matrix and scipy.sparse.csr_array, \
shape=(n_samples, n_features)
The input matrix or array to be modified inplace.
Examples
--------
>>> from scipy.sparse import csr_matrix
>>> from sklearn.utils.sparsefuncs_fast import inplace_csr_row_normalize_l1
>>> import numpy as np
>>> indptr = np.array([0, 2, 3, 4])
>>> indices = np.array([0, 1, 2, 3])
>>> data = np.array([1.0, 2.0, 3.0, 4.0])
>>> X = csr_matrix((data, indices, indptr), shape=(3, 4))
>>> X.toarray()
array([[1., 2., 0., 0.],
[0., 0., 3., 0.],
[0., 0., 0., 4.]])
>>> inplace_csr_row_normalize_l1(X)
>>> X.toarray()
array([[0.33... , 0.66... , 0. , 0. ],
[0. , 0. , 1. , 0. ],
[0. , 0. , 0. , 1. ]])
"""
_inplace_csr_row_normalize_l1(X.data, X.shape, X.indices, X.indptr)
def _inplace_csr_row_normalize_l1(
floating[:] X_data,
shape,
const integral[:] X_indices,
const integral[:] X_indptr,
):
cdef:
uint64_t n_samples = shape[0]
# the column indices for row i are stored in:
# indices[indptr[i]:indices[i+1]]
# and their corresponding values are stored in:
# data[indptr[i]:indptr[i+1]]
uint64_t i
integral j
double sum_
for i in range(n_samples):
sum_ = 0.0
for j in range(X_indptr[i], X_indptr[i + 1]):
sum_ += fabs(X_data[j])
if sum_ == 0.0:
# do not normalize empty rows (can happen if CSR is not pruned
# correctly)
continue
for j in range(X_indptr[i], X_indptr[i + 1]):
X_data[j] /= sum_
def inplace_csr_row_normalize_l2(X):
"""Normalize inplace the rows of a CSR matrix or array by their L2 norm.
Parameters
----------
X : scipy.sparse.csr_matrix, shape=(n_samples, n_features)
The input matrix or array to be modified inplace.
Examples
--------
>>> from scipy.sparse import csr_matrix
>>> from sklearn.utils.sparsefuncs_fast import inplace_csr_row_normalize_l2
>>> import numpy as np
>>> indptr = np.array([0, 2, 3, 4])
>>> indices = np.array([0, 1, 2, 3])
>>> data = np.array([1.0, 2.0, 3.0, 4.0])
>>> X = csr_matrix((data, indices, indptr), shape=(3, 4))
>>> X.toarray()
array([[1., 2., 0., 0.],
[0., 0., 3., 0.],
[0., 0., 0., 4.]])
>>> inplace_csr_row_normalize_l2(X)
>>> X.toarray()
array([[0.44... , 0.89... , 0. , 0. ],
[0. , 0. , 1. , 0. ],
[0. , 0. , 0. , 1. ]])
"""
_inplace_csr_row_normalize_l2(X.data, X.shape, X.indices, X.indptr)
def _inplace_csr_row_normalize_l2(
floating[:] X_data,
shape,
const integral[:] X_indices,
const integral[:] X_indptr,
):
cdef:
uint64_t n_samples = shape[0]
uint64_t i
integral j
double sum_
for i in range(n_samples):
sum_ = 0.0
for j in range(X_indptr[i], X_indptr[i + 1]):
sum_ += (X_data[j] * X_data[j])
if sum_ == 0.0:
# do not normalize empty rows (can happen if CSR is not pruned
# correctly)
continue
sum_ = sqrt(sum_)
for j in range(X_indptr[i], X_indptr[i + 1]):
X_data[j] /= sum_
def assign_rows_csr(
X,
const intptr_t[:] X_rows,
const intptr_t[:] out_rows,
floating[:, ::1] out,
):
"""Densify selected rows of a CSR matrix into a preallocated array.
Like out[out_rows] = X[X_rows].toarray() but without copying.
No-copy supported for both dtype=np.float32 and dtype=np.float64.
Parameters
----------
X : scipy.sparse.csr_matrix, shape=(n_samples, n_features)
X_rows : array, dtype=np.intp, shape=n_rows
out_rows : array, dtype=np.intp, shape=n_rows
out : array, shape=(arbitrary, n_features)
"""
cdef:
# intptr_t (npy_intp, np.intp in Python) is what np.where returns,
# but int is what scipy.sparse uses.
intp_t i, ind, j, k
intptr_t rX
const floating[:] data = X.data
const int32_t[:] indices = X.indices
const int32_t[:] indptr = X.indptr
if X_rows.shape[0] != out_rows.shape[0]:
raise ValueError("cannot assign %d rows to %d"
% (X_rows.shape[0], out_rows.shape[0]))
with nogil:
for k in range(out_rows.shape[0]):
out[out_rows[k]] = 0.0
for i in range(X_rows.shape[0]):
rX = X_rows[i]
for ind in range(indptr[rX], indptr[rX + 1]):
j = indices[ind]
out[out_rows[i], j] = data[ind]
@@ -0,0 +1,345 @@
//-----------------------------------------------------------------------------
// MurmurHash3 was written by Austin Appleby, and is placed in the public
// domain. The author hereby disclaims copyright to this source code.
// Note - The x86 and x64 versions do _not_ produce the same results, as the
// algorithms are optimized for their respective platforms. You can still
// compile and run any of them on any platform, but your performance with the
// non-native version will be less than optimal.
#include "MurmurHash3.h"
//-----------------------------------------------------------------------------
// Platform-specific functions and macros
// Microsoft Visual Studio
#if defined(_MSC_VER)
#define FORCE_INLINE __forceinline
#include <stdlib.h>
#define ROTL32(x,y) _rotl(x,y)
#define ROTL64(x,y) _rotl64(x,y)
#define BIG_CONSTANT(x) (x)
// Other compilers
#else // defined(_MSC_VER)
#if defined(GNUC) && ((GNUC > 4) || (GNUC == 4 && GNUC_MINOR >= 4))
/* gcc version >= 4.4 4.1 = RHEL 5, 4.4 = RHEL 6.
* Don't inline for RHEL 5 gcc which is 4.1 */
#define FORCE_INLINE attribute((always_inline))
#else
#define FORCE_INLINE
#endif
inline uint32_t rotl32 ( uint32_t x, int8_t r )
{
return (x << r) | (x >> (32 - r));
}
inline uint64_t rotl64 ( uint64_t x, int8_t r )
{
return (x << r) | (x >> (64 - r));
}
#define ROTL32(x,y) rotl32(x,y)
#define ROTL64(x,y) rotl64(x,y)
#define BIG_CONSTANT(x) (x##LLU)
#endif // !defined(_MSC_VER)
//-----------------------------------------------------------------------------
// Block read - if your platform needs to do endian-swapping or can only
// handle aligned reads, do the conversion here
FORCE_INLINE uint32_t getblock ( const uint32_t * p, int i )
{
return p[i];
}
FORCE_INLINE uint64_t getblock ( const uint64_t * p, int i )
{
return p[i];
}
//-----------------------------------------------------------------------------
// Finalization mix - force all bits of a hash block to avalanche
FORCE_INLINE uint32_t fmix ( uint32_t h )
{
h ^= h >> 16;
h *= 0x85ebca6b;
h ^= h >> 13;
h *= 0xc2b2ae35;
h ^= h >> 16;
return h;
}
//----------
FORCE_INLINE uint64_t fmix ( uint64_t k )
{
k ^= k >> 33;
k *= BIG_CONSTANT(0xff51afd7ed558ccd);
k ^= k >> 33;
k *= BIG_CONSTANT(0xc4ceb9fe1a85ec53);
k ^= k >> 33;
return k;
}
//-----------------------------------------------------------------------------
void MurmurHash3_x86_32 ( const void * key, int len,
uint32_t seed, void * out )
{
const uint8_t * data = (const uint8_t*)key;
const int nblocks = len / 4;
uint32_t h1 = seed;
uint32_t c1 = 0xcc9e2d51;
uint32_t c2 = 0x1b873593;
//----------
// body
const uint32_t * blocks = (const uint32_t *)(data + nblocks*4);
for(int i = -nblocks; i; i++)
{
uint32_t k1 = getblock(blocks,i);
k1 *= c1;
k1 = ROTL32(k1,15);
k1 *= c2;
h1 ^= k1;
h1 = ROTL32(h1,13);
h1 = h1*5+0xe6546b64;
}
//----------
// tail
const uint8_t * tail = (const uint8_t*)(data + nblocks*4);
uint32_t k1 = 0;
switch(len & 3)
{
case 3: k1 ^= tail[2] << 16;
case 2: k1 ^= tail[1] << 8;
case 1: k1 ^= tail[0];
k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1;
}
//----------
// finalization
h1 ^= len;
h1 = fmix(h1);
*(uint32_t*)out = h1;
}
//-----------------------------------------------------------------------------
void MurmurHash3_x86_128 ( const void * key, const int len,
uint32_t seed, void * out )
{
const uint8_t * data = (const uint8_t*)key;
const int nblocks = len / 16;
uint32_t h1 = seed;
uint32_t h2 = seed;
uint32_t h3 = seed;
uint32_t h4 = seed;
uint32_t c1 = 0x239b961b;
uint32_t c2 = 0xab0e9789;
uint32_t c3 = 0x38b34ae5;
uint32_t c4 = 0xa1e38b93;
//----------
// body
const uint32_t * blocks = (const uint32_t *)(data + nblocks*16);
for(int i = -nblocks; i; i++)
{
uint32_t k1 = getblock(blocks,i*4+0);
uint32_t k2 = getblock(blocks,i*4+1);
uint32_t k3 = getblock(blocks,i*4+2);
uint32_t k4 = getblock(blocks,i*4+3);
k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1;
h1 = ROTL32(h1,19); h1 += h2; h1 = h1*5+0x561ccd1b;
k2 *= c2; k2 = ROTL32(k2,16); k2 *= c3; h2 ^= k2;
h2 = ROTL32(h2,17); h2 += h3; h2 = h2*5+0x0bcaa747;
k3 *= c3; k3 = ROTL32(k3,17); k3 *= c4; h3 ^= k3;
h3 = ROTL32(h3,15); h3 += h4; h3 = h3*5+0x96cd1c35;
k4 *= c4; k4 = ROTL32(k4,18); k4 *= c1; h4 ^= k4;
h4 = ROTL32(h4,13); h4 += h1; h4 = h4*5+0x32ac3b17;
}
//----------
// tail
const uint8_t * tail = (const uint8_t*)(data + nblocks*16);
uint32_t k1 = 0;
uint32_t k2 = 0;
uint32_t k3 = 0;
uint32_t k4 = 0;
switch(len & 15)
{
case 15: k4 ^= tail[14] << 16;
case 14: k4 ^= tail[13] << 8;
case 13: k4 ^= tail[12] << 0;
k4 *= c4; k4 = ROTL32(k4,18); k4 *= c1; h4 ^= k4;
case 12: k3 ^= tail[11] << 24;
case 11: k3 ^= tail[10] << 16;
case 10: k3 ^= tail[ 9] << 8;
case 9: k3 ^= tail[ 8] << 0;
k3 *= c3; k3 = ROTL32(k3,17); k3 *= c4; h3 ^= k3;
case 8: k2 ^= tail[ 7] << 24;
case 7: k2 ^= tail[ 6] << 16;
case 6: k2 ^= tail[ 5] << 8;
case 5: k2 ^= tail[ 4] << 0;
k2 *= c2; k2 = ROTL32(k2,16); k2 *= c3; h2 ^= k2;
case 4: k1 ^= tail[ 3] << 24;
case 3: k1 ^= tail[ 2] << 16;
case 2: k1 ^= tail[ 1] << 8;
case 1: k1 ^= tail[ 0] << 0;
k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1;
}
//----------
// finalization
h1 ^= len; h2 ^= len; h3 ^= len; h4 ^= len;
h1 += h2; h1 += h3; h1 += h4;
h2 += h1; h3 += h1; h4 += h1;
h1 = fmix(h1);
h2 = fmix(h2);
h3 = fmix(h3);
h4 = fmix(h4);
h1 += h2; h1 += h3; h1 += h4;
h2 += h1; h3 += h1; h4 += h1;
((uint32_t*)out)[0] = h1;
((uint32_t*)out)[1] = h2;
((uint32_t*)out)[2] = h3;
((uint32_t*)out)[3] = h4;
}
//-----------------------------------------------------------------------------
void MurmurHash3_x64_128 ( const void * key, const int len,
const uint32_t seed, void * out )
{
const uint8_t * data = (const uint8_t*)key;
const int nblocks = len / 16;
uint64_t h1 = seed;
uint64_t h2 = seed;
uint64_t c1 = BIG_CONSTANT(0x87c37b91114253d5);
uint64_t c2 = BIG_CONSTANT(0x4cf5ad432745937f);
//----------
// body
const uint64_t * blocks = (const uint64_t *)(data);
for(int i = 0; i < nblocks; i++)
{
uint64_t k1 = getblock(blocks,i*2+0);
uint64_t k2 = getblock(blocks,i*2+1);
k1 *= c1; k1 = ROTL64(k1,31); k1 *= c2; h1 ^= k1;
h1 = ROTL64(h1,27); h1 += h2; h1 = h1*5+0x52dce729;
k2 *= c2; k2 = ROTL64(k2,33); k2 *= c1; h2 ^= k2;
h2 = ROTL64(h2,31); h2 += h1; h2 = h2*5+0x38495ab5;
}
//----------
// tail
const uint8_t * tail = (const uint8_t*)(data + nblocks*16);
uint64_t k1 = 0;
uint64_t k2 = 0;
switch(len & 15)
{
case 15: k2 ^= uint64_t(tail[14]) << 48;
case 14: k2 ^= uint64_t(tail[13]) << 40;
case 13: k2 ^= uint64_t(tail[12]) << 32;
case 12: k2 ^= uint64_t(tail[11]) << 24;
case 11: k2 ^= uint64_t(tail[10]) << 16;
case 10: k2 ^= uint64_t(tail[ 9]) << 8;
case 9: k2 ^= uint64_t(tail[ 8]) << 0;
k2 *= c2; k2 = ROTL64(k2,33); k2 *= c1; h2 ^= k2;
case 8: k1 ^= uint64_t(tail[ 7]) << 56;
case 7: k1 ^= uint64_t(tail[ 6]) << 48;
case 6: k1 ^= uint64_t(tail[ 5]) << 40;
case 5: k1 ^= uint64_t(tail[ 4]) << 32;
case 4: k1 ^= uint64_t(tail[ 3]) << 24;
case 3: k1 ^= uint64_t(tail[ 2]) << 16;
case 2: k1 ^= uint64_t(tail[ 1]) << 8;
case 1: k1 ^= uint64_t(tail[ 0]) << 0;
k1 *= c1; k1 = ROTL64(k1,31); k1 *= c2; h1 ^= k1;
}
//----------
// finalization
h1 ^= len; h2 ^= len;
h1 += h2;
h2 += h1;
h1 = fmix(h1);
h2 = fmix(h2);
h1 += h2;
h2 += h1;
((uint64_t*)out)[0] = h1;
((uint64_t*)out)[1] = h2;
}
//-----------------------------------------------------------------------------
@@ -0,0 +1,45 @@
//-----------------------------------------------------------------------------
// MurmurHash3 was written by Austin Appleby, and is placed in the public
// domain. The author hereby disclaims copyright to this source code.
#ifndef _MURMURHASH3_H_
#define _MURMURHASH3_H_
//-----------------------------------------------------------------------------
// Platform-specific functions and macros
// Microsoft Visual Studio
#if defined(_MSC_VER)
typedef unsigned char uint8_t;
typedef unsigned long uint32_t;
typedef unsigned __int64 uint64_t;
// Other compilers
#else // defined(_MSC_VER)
#include <stdint.h>
#endif // !defined(_MSC_VER)
//-----------------------------------------------------------------------------
#ifdef __cplusplus
extern "C" {
#endif
void MurmurHash3_x86_32 ( const void * key, int len, uint32_t seed, void * out );
void MurmurHash3_x86_128 ( const void * key, int len, uint32_t seed, void * out );
void MurmurHash3_x64_128 ( const void * key, int len, uint32_t seed, void * out );
#ifdef __cplusplus
}
#endif
//-----------------------------------------------------------------------------
#endif // _MURMURHASH3_H_