Kwker

Core concepts

Kwker calls work like the NumPy, pandas and PyTorch calls you already use, and return the same results. This page covers what you control: whether a call changes your array, how many threads it uses, the sort order, memory and errors. Edge cases come last.

In place or a copy

import numpy as np
import kwker

a = np.array([5, 3, 9, 1], dtype=np.uint32)
b = kwker.sorted(a)          # a sorted copy; a is unchanged
kwker.sort(a)                # a itself is sorted now
print(a, b)

keys = np.array([3.0, 1.0, 2.0])
values = np.array([30, 10, 20])
kwker.sort_kv(keys, values)  # values move with their keys
print(keys, values)
[1 3 5 9] [1 3 5 9]
[1. 2. 3.] [10 20 30]

Threads

A call runs on the calling thread. Kwker starts no threads unless you ask:

Orders

Calls that sort, rank or pick values take an order: smallest first (the default) or largest first.

Python Rust C C++ JavaScript Meaning
descending=False (default) Order::ASCENDING KWKER_ASCENDING (0) Order::ascending {} smallest first
descending=True Order::DESCENDING KWKER_DESCENDING Order::descending { descending: true } largest first
nans_first=True Order { nans: NanPlacement::First, .. } KWKER_NANS_FIRST Order::nans_first { nansFirst: true } NaN values first

NaN values go last in both directions, as in NumPy, unless you ask for them first.

Supported types

Integers of 8 to 64 bits (128 in Rust and C), float16, bfloat16, FP8 (E4M3, E5M2), float32, float64, packed int4 and byte strings. In Python: every NumPy integer and float dtype, bfloat16 and FP8 through ml_dtypes, datetime64 / timedelta64 (NaT last) and NumPy string arrays.

Engines

Kwker picks the fastest code for your CPU when the program starts: avx512, avx2 or sse42 on x86, neon (with sve where the CPU has it) on ARM, portable elsewhere. Every engine returns the same results; you never choose one.

PythonRuns on your machine.
import kwker
print(kwker.isa())               # the engine in use
kwker.set_isa("portable")        # cap it at run time (debugging, A/B timings)
kwker.set_isa(None)              # back to the best

To try a slower engine, set KWKER_ISA=avx2 (or portable) before the program starts.

Memory

Most calls need little extra memory, and each call's bound is documented (in Rust, scratch_bound() returns it).

PythonRuns on your machine.
import numpy as np, kwker
plan = kwker.Plan(descending=True)
for _ in range(3):
    idx = plan.argsort(np.random.default_rng(0).integers(0, 1000, 5000))   # buffers reused across calls

Errors

A rejected call changes nothing. Typical causes: k out of range, keys and values of different lengths, an unsupported type, a null pointer. Each language reports them its usual way:

Language Invalid arguments Other failures
Python ValueError, TypeError (an unsupported dtype) kwker.Cancelled when a progress callback cancels
Rust a panic, only on the contract violations each function documents std::io::Error from the file sorts
C return code -1 (0 on success); the C API reference lists each function's codes the same codes
C++ std::invalid_argument std::runtime_error for I/O errors
Go a panic, as for an out-of-range slice index an error from SortFile, SetISA and Observe
JavaScript (Node.js) TypeError (not a supported typed array), RangeError (k out of range) Error
JavaScript (WebAssembly) TypeError, RangeError RangeError when WebAssembly memory runs out
Java IllegalArgumentException (NullPointerException for null)
C# ArgumentException, ArgumentOutOfRangeException, ArgumentNullException
Ruby ArgumentError, TypeError (an unsupported array), FrozenError (a frozen array) NoMemoryError
PHP ValueError, TypeError (an array of both ints and floats)
Perl croak (a die with the message)
R an R error (stop())
Swift a precondition failure (the program stops)
Zig error.InvalidArgument
Fortran error stop with the message
COBOL RETURNING -1 (the C codes)

Equal values

Equal values matter only when a call returns positions or carries other data along:

Calls What happens to equal values
sort, sorted, select, partial_sort Not observable: equal values are identical
argsort, top_k, argselect, argpartition, rank(method="ordinal"), lexsort Input order (stable)
sort_kv(stable=False) (the default) The values of equal keys in any order
sort_kv(stable=True), kway_merge, group_codes Input order

The same input gives the same output on every CPU and with any number of threads.

Special float values

Floats sort by value, with -0.0 before +0.0, -inf and inf at the ends, and NaN values together at the end you choose. NaN values come back bit for bit as they went in. Calls that compare values (ranks, groups, set operations, unique) treat -0.0 and +0.0 as one value, as NumPy does. Subnormal values are compared exactly, even when another library has switched the CPU to flush-to-zero mode. The same holds for every float type, from FP8 to float64.

import numpy as np
import kwker

a = np.array([2.0, np.nan, -0.0, 0.0, -1.0, np.inf])
print(kwker.sorted(a))
print(kwker.sorted(a, descending=True))
print(kwker.sorted(a, descending=True, nans_first=True))
[-1. -0.  0.  2. inf nan]
[inf  2.  0. -0. -1. nan]
[nan inf  2.  0. -0. -1.]