// STRYKE-POLARS — POLARS + NDARRAY + LINALG + FFT + RANDOM // STRYKE PACKAGE

v0.22.0 · 1417 cdylib exports · 1505 .stk wrapper fns · 46 .stk wrapper packages · 167 Rust unit tests · dlopen'd in-process on use Polars · opt-in (kept out of stryke core)

Report GitHub Issues
// Color scheme

>_STRYKE-POLARS

The full pandas + numpy + scipy-class surface in one cdylib. No core bloat. pandas DataFrame + Series + Index + IO + Categorical, numpy ndarray + ufuncs + linalg + random + fft + polynomial + masked arrays + datetime64, plus signal processing, statistical tests, image filters, ML metrics, graph algorithms, time-series analytics, sparse matrix, distance / clustering, JSON / bit / format helpers — all in a single cdylib, dlopen'd in-process by stryke via use Polars.

v0.22.0
Current release
1417
cdylib exports
46
.stk packages
167
Rust unit tests
0
clippy warnings (-D warnings)
0
missing-docs (#![deny])

Install

# build the cdylib + install as a stryke package
git clone https://github.com/MenkeTechnologies/stryke-polars
cd stryke-polars
make install        # cargo build --release && s pkg install -g .

# or from a published GitHub release
s pkg install -g github:MenkeTechnologies/stryke-polars

# verify
echo 'use Polars; say Polars::version()->{version}' | stryke

The cdylib (libstryke_polars.dylib on macOS, .so on Linux) lands under ~/.stryke/store/stryke-polars@<ver>/ (per make install). use Polars resolves the installed package, dlopen's it once per stryke session, and binds the FFI entry points lazily. Core stryke never links against polars/ndarray/openblas/rustfft.

Quick start

Every wrapper is namespaced under a per-family stryke package: use Polars::DataFrame binds Polars::DataFrame::*, use Polars::NdArray binds Polars::NdArray::*, and so on (one package per lib/*.stk module). DataFrame ops are stateless — each call takes the raw column hash (a reference, \%cols), not a previously-built handle.

use Polars
use Polars::DataFrame

# version blob: { version, polars, ndarray }
val $v = Polars::version()
p "stryke-polars $v->{version} (polars $v->{polars}, ndarray $v->{ndarray})"

# DataFrame from a column dict (key -> array)
val %cols = (
    ticker => ["AAPL", "MSFT", "NVDA"],
    px     => [231.4, 422.1, 138.9],
)

# new() validates the shape and returns a { shape, columns } blob
val $meta = Polars::DataFrame::new(\%cols)

val ($nrows, $ncols) = @{ Polars::DataFrame::shape(\%cols) }   # => (3, 2)
val @columns = @{ Polars::DataFrame::columns(\%cols) }          # => (ticker, px)
val @types   = @{ Polars::DataFrame::dtypes(\%cols) }
val $head    = Polars::DataFrame::head(\%cols, 2)
val $proj    = Polars::DataFrame::select(\%cols, ["ticker"])

numpy-style ndarray math through the NdArray package:

use Polars::NdArray

val $a = Polars::NdArray::arange(0, 12, 1)
val $b = Polars::NdArray::reshape($a, [3, 4])
val $s = Polars::NdArray::sum($a)    # => 66
val $m = Polars::NdArray::mean($a)   # => 5.5

group-by aggregations (from examples/groupby.stk):

use Polars::DataFrame

val %sales = (
    region  => ["east", "east", "west", "west", "north", "north"],
    revenue => [100, 150, 90, 130, 60, 85],
    units   => [10, 12, 9, 11, 6, 8],
)

val $by_region   = Polars::DataFrame::groupby_sum(\%sales, ["region"])
val $by_region_m = Polars::DataFrame::groupby_mean(\%sales, ["region"])
val $by_region_c = Polars::DataFrame::groupby_count(\%sales, ["region"])

Two self-contained, runnable tours ship in examples/: discover.stk (shape / head / select / columns / dtypes / to_json) and groupby.stk (multi-key aggregations). Run either with s examples/<name>.stk. The full per-family surface is exercised by the suites in t/ (s test t/).

Surface — pandas + numpy core

Counts below are the live cdylib export totals per family prefix (grep -c 'pub extern "C" fn polars__<prefix>_' src/*.rs). The cdylib prefix column is the flat FFI symbol prefix; the stryke-side surface is the namespaced Polars::<Family>::<verb> package form (see the Naming Convention).

FamilyStryke packagecdylib prefixShipped
pandas Series (+ .str.* / .dt.*)Polars::Seriespolars__sr_*206
numpy ndarrayPolars::NdArray / NdArrayExtpolars__arr_*177
pandas DataFramePolars::DataFrame / DataFrameExtpolars__df_*119
numpy ufuncsPolars::Ufunc / UfuncExtpolars__np_*94
pandas Index / MultiIndex / DatetimeIndexPolars::Indexpolars__idx_*69
numpy masked arraysPolars::Maskedpolars__ma_*61
numpy random distributionsPolars::Random / RandomExtpolars__rand_*40
numpy linalgPolars::Linalg / LinalgExtpolars__linalg_*37
pandas CategoricalPolars::Categoricalpolars__cat_*35
numpy datetime64 / timedelta64Polars::DateTime64polars__dt64_*32
pandas IO (read/write CSV, JSON, JSONL, TSV, HTML, Markdown)Polars::IOpolars__pd_*29
numpy polynomial (Chebyshev, Hermite, Legendre, Laguerre…)Polars::Polynomial / PolynomialExtpolars__poly_*17
pandas DataFrame .str.* accessorPolars::Textpolars__str_*16
numpy fftPolars::FFT / FFTExtpolars__fft_*13
pandas DataFrame .dt.* accessorPolars::DateTime64polars__dt_*13
dtype parsing / canonicalizationPolars::DataFramepolars__parse_*1
Pandas + numpy subtotal959

Surface — scipy-class extensions

Beyond pandas + numpy parity, the package ships a scipy-class extension layer. Every entry is a real Rust implementation backing the listed .stk package; counts are the live per-prefix export totals.

FamilyStryke packagecdylib prefixShipped
Image filters (blur, Sobel/Prewitt/Laplacian, morphology, rotate, resize)Polars::Imagepolars__img_*42
Distances + distribution PDFs/CDFs (Euclidean, Manhattan, Minkowski, Cosine, Jaccard, Mahalanobis, …)Polars::Distpolars__dist_*35
Signal processing (convolve, correlate, smoothing, peak finding, MACD, RSI, Bollinger)Polars::Signalpolars__sig_*35
Bit manipulation (popcount, rotate, MSB/LSB, base conversion, …)Polars::Bitpolars__bit_*31
Text / NLP (tokenize, ngrams, Levenshtein, LCS, camel↔snake)Polars::Textpolars__text_*27
Statistical tests (t-test, chi², Mann-Whitney, KS, ANOVA, Levene, ACF/PACF, linregress)Polars::Stattestpolars__stattest_*24
Statistics (zscore, IQR, skew/kurt, pearson/spearman, MAE/MSE/R², KL/JS divergence)Polars::Statpolars__stat_*24
Misc math (factorial, choose, Fibonacci, primes, GCD/LCM, modpow, …)Polars::Miscpolars__misc_*23
ML metrics (accuracy, precision/recall/F1, ROC AUC, MCC, Cohen kappa, …)Polars::Metricpolars__metric_*23
Groupby aggregationsPolars::GroupBypolars__gb_*21
Hashing (djb2/djb2a, FNV-1/1a 64+32, Jenkins, CRC32, MurmurHash3-32, BKDR, RS, JS, AP, DEK, PJW, ELF, SAX, Fletcher-16)Polars::Hashpolars__hash_*18
Formatting (round, percent, currency, scientific, commas, human bytes, ordinal, Roman)Polars::Fmtpolars__fmt_*18
Window functions (Hann, Hamming, Blackman, Kaiser, Tukey, Lanczos, Parzen, …)Polars::Windowpolars__win_*17
Sparse matrices (COO format)Polars::Sparsepolars__sparse_*15
JSON helpers (parse, stringify, path, keys/values, pluck, merge)Polars::Jsonpolars__json_*14
Time-series analytics (lag, diff, log/simple returns, drawdown, Sharpe, Sortino, VaR/CVaR)Polars::TSpolars__ts_*13
Graph algorithms (BFS, DFS, Dijkstra, connected components, Floyd-Warshall, PageRank)Polars::Graphpolars__graph_*13
Geometric (haversine, bearing, polygon area/perimeter, point-in-polygon, rotate/translate)Polars::Geopolars__geo_*12
Boolean array opsPolars::Boolpolars__bool_*11
Set opsPolars::Setpolars__set_*10
Encoding (one-hot, label, binary, target, base64, hex, URL)Polars::Encodingpolars__enc_*8
Checksums (XOR, simple, Adler-32, BSD-16, internet)Polars::Checksumpolars__sum_*7
Interpolation (linear, nearest, zero-order, cubic-natural, log, bilinear grid)Polars::Interppolars__interp_*6
Optimization (bisection, golden section, Newton-Raphson, secant, gradient descent)Polars::Optpolars__opt_*5
Clustering (k-means, silhouette, DBSCAN neighbors)Polars::Clusterpolars__cluster_*5
scipy-class subtotal457
Grand total (+ polars__version)1417

Per-family .stk wrappers under lib/ expose every cdylib symbol as a typed Polars::<Family>::<verb> stryke fn — 46 packages, 1505 wrapper fns total. See the lib/ directory for the wrapper sources and the README API Reference for the package list. The 41 cdylib prefix families map onto these 46 packages (several families split a base + Ext package, e.g. arr_*NdArray + NdArrayExt).

Why a package, not a builtin

stryke's core stays small on purpose — most one-liner / awk-replacement work doesn't need 200+ transitive crates of pandas + numpy machinery. The full DataFrame + ndarray + linalg + FFT surface hits a different scale: heavy deps, narrow use cases. The opt-in package tier exists precisely for this shape of code.

Parquet / Arrow IO routes through stryke-arrow to share a single arrow-rs link in-process — stryke-polars does not link arrow-rs directly to avoid dlsym conflicts.

Quality gates

Every commit on main runs through the CI matrix in .github/workflows/ci.yml. Pull requests must clear the same gates:

GateInvariant
cargo fmt --all --checkNo drift from rustfmt; every file fmt-clean
cargo clippy --all-targets --locked -- -D warningsZero clippy warnings (no module-level #![allow] escape hatches)
cargo test --locked --no-fail-fast167 hand-crafted unit tests, all green on macos-latest + ubuntu-latest
RUSTDOCFLAGS=-D warnings cargo doc --no-depsRustdoc clean; #![deny(missing_docs)] in lib.rs means every public item carries a /// comment
Release build (3 targets)x86_64-unknown-linux-gnu, x86_64-apple-darwin, aarch64-apple-darwin — cdylib uploaded as build artifact
Contract gatesREADME newline + badge + h2 + https-link checks, workflow newline + no-tab checks, shell-script shebang + executable bit checks

The 167 Rust unit tests assert mathematical correctness against canonical values — F(30) = 832,040, Levenshtein(kitten, sitting) = 3, Chebyshev L distance = max(|x−y|), Haversine NYC→LA ≈ 3936 km, Adler-32("Wikipedia") = 0x11E60398 (RFC 1950 vector), and so on. The ffi_test::call harness in lib.rs exercises every test through the production JSON-over-FFI envelope (CString round-trip, catch_unwind on panic, allocation freed via stryke_free_cstring) so the tests cover the FFI seam alongside the math.

FFI layer

Each polars__* export takes a single *const c_char (NUL-terminated JSON args) and returns a *mut c_char (NUL-terminated JSON result). The cdylib owns the returned allocation; the stryke side must release it via the cdylib-exported stryke_free_cstring. stryke's rust_ffi::load_cdylib wires this automatically.

JSON envelope on success is the per-fn shape. JSON envelope on error is {"error": "<message>"}. Panics inside the cdylib are caught via catch_unwind and surfaced as errors — the stryke runtime never crashes on a cdylib bug.

Naming convention

Stryke-side wrappers are namespaced packages, one per lib/*.stk module: use Polars::DataFrame binds Polars::DataFrame::head, use Polars::Linalg binds Polars::Linalg::*, and so on. The cdylib-side FFI symbols are flat, double-underscore-namespaced polars__ plus a per-family verb prefix:

cdylib symbol shapeFamily
polars__df_<verb>DataFrame
polars__sr_<verb>Series
polars__arr_<verb>ndarray
polars__np_<verb>ufuncs
polars__idx_<verb>Index
polars__linalg_<verb> / polars__rand_<verb> / polars__fft_<verb> / polars__poly_<verb> / polars__ma_<verb> / polars__dt64_<verb>namespaced numpy families
polars__pd_read_<fmt> / polars__pd_to_<fmt>pandas IO

Backing crates

The heavy numerics live in the cdylib only; core stryke never links any of them.

SubsystemBacking crate(s)
DataFrame / Series / Index / pandas IOpolars 0.45 (full feature set)
ndarray + ufuncsndarray 0.16 + rayon
linalgnalgebra 0.33 (pure-Rust SVD / QR / LU / Cholesky / Eigen / solve / inv / det)
randomrand + rand_distr + ndarray-rand + rand_chacha
fftrustfft 6.2 + realfft 3.4
polynomialhand-rolled on ndarray (recurrence formulas)
masked arraysndarray (mask vec parallel to data)
datetime64 / timedelta64chrono + chrono-tz 0.10
Decimal dtyperust_decimal 1.36
FFI envelopeserde_json, anyhow, std::panic::catch_unwind

The linalg subsystem uses pure-Rust nalgebra rather than ndarray-linalg / OpenBLAS to keep the dependency chain vendorable and avoid an openblas-srcopenblas-build toolchain requirement at build time.

Layout

stryke-polars/
├── Cargo.toml             # crate-type = ["cdylib"]
├── src/
│   ├── lib.rs            # FFI plumbing + polars__version
│   └── df.rs / sr.rs / nd.rs / linalg.rs / ...  # per-family modules
├── lib/                   # stryke .stk wrappers (one per family)
├── stryke.toml            # stryke package manifest
├── t/                     # stryke-side integration tests (s test t/)
├── tests/                 # Rust-side cargo tests + contract gates
├── examples/              # runnable .stk examples
├── Makefile               # `make install` builds + installs
└── docs/                  # this site (GitHub Pages)

Sibling packages

Part of the stryke package family. Browse the others via the MenkeTechnologiesMeta umbrella repo: