libjay for Python
Independent, modern implementations of the J
and APL array languages, embedded in Python. Not a framework: the
relationship to your code is the one re has — a small language inside a
string literal, compiled once, run many times.
import jay
jay.j("+/ 1 2 3 4") # 10 — "+/" inserts + between the numbers
jay.j("(+/ % #) {x}", {"x": [3.0, 1.0, 4.0, 1.0, 5.0]}) # 2.8 — the mean
The mean is written as a fork: sum (+/) divided by (%) count (#). No
loops, no axis keyword arguments, no intermediate allocations to name — the
expression is the dataflow graph, which is what lets libjay fuse and
parallelise it. jay.apl is the same entry point for APL, with its own
semantics (J reduces along the leading axis, APL along the trailing one).
Install
uvx libjay -e '(+/ % #) 3 1 4 1 5' # try the CLI with no install
uv add libjay # or: pip install libjay
From a checkout today (Rust toolchain required):
uv venv && uv pip install maturin
uv run maturin develop
The names follow the pillow/PIL convention: the package (and the CLI) is
libjay, the import is jay — matching Rust (use jay::) and C (-ljay,
jay.h). Wheels are abi3, Python 3.10+, and have no runtime dependencies.
Compile once, bind data, run
import jay
k = jay.j.compile("+/ {weights} * {data}")
k({"weights": w, "data": chunk1})
k({"weights": w, "data": chunk2})
k2 = k.bind({"weights": w}) # a new kernel; w rides along
k2({"data": chunk3}) # only the changing part at call time
jay.j(...) is the one-shot form: compile, bind and execute in one call.
Kernels are immutable — bind returns a new one — and the compiled program
is shared and safe to run from several threads.
Compiling the same source twice does not compile it twice: programs are
memoised in the process, so the one-shot form is cheap to call in a loop.
jay.clear_cache() empties the table if you ever need it emptied; nothing
is written to disk.
On Python 3.14+, t-strings make the same thing typo-safe — interpolated values become both the type contract and the defaults:
k = jay.j.compile(t"+/ {weights} * {data}")
k() # computes on the interpolated samples
k({"data": other}) # override at call time
Braces always mean data binding, never splicing text into the program.
Errors point into your expression, in both languages:
length error: arguments do not agree: left shape 2, right shape 3
1 2 + 1 2 3
^^^^^^^^^^^
note: frames first differ at axis 0: 2 vs 3
Real data, zero-copy
Polars, pandas 2, PyArrow and numpy work natively — no dependency on any of
them, via the Arrow C data interface and __array_interface__. libjay is
not a replacement for Polars or pandas: you stay in them for everything
tabular and hand libjay the numeric block where the heavy mathematics lives.
import numpy as np, polars as pl
df = pl.DataFrame({"open": [...], "close": [...]}) # M rows × N columns
jay.j("+/ {df}", {"df": df}) # each column summed over all rows
jay.j('+/"1 {df}', {"df": df}) # each row summed
v = jay.j("2 * {x}", {"x": np.arange(10**8)}) # zero-copy in
pl.Series(v) # zero-copy out
int64/float64 data (and timestamps/durations, which are physically int64) crosses the boundary without copying, and the kernel keeps the source alive. Narrower types widen with one copy. Columns with nulls, tables mixing int64 with float64, and non-contiguous numpy views are refused with an error that names the column and suggests the cast — where information is missing, libjay reports and stops rather than guessing on your behalf. The full table of what is zero-copy, copied, refused and not supported yet is in docs/coverage.md.
Boxes, and lists of strings
A Python list whose items don't share one shape — a list of strings, a ragged list of lists — becomes a boxed array on the way in, and a boxed result converts back to nested Python data on the way out:
jay.j("# &.> {names}", {"names": ["ab", "cde"]}).tolist() # [2, 3]
jay.j("{names}", {"names": ["ab", "cde"]}).tolist() # ['ab', 'cde']
Complex numbers
Both languages' arithmetic runs on complex values; numpy.complex128
crosses the boundary zero-copy and a scalar result is a Python complex:
jay.j("{z} * {z}", {"z": 3 + 4j}) # (-7+24j)
jay.j("%: _4") # 2j — square root of a negative
Big integers and exact rationals
J's exact types — x: for arbitrary-precision integers, r for exact
ratios — cross as Python's int and fractions.Fraction, both ways:
jay.j("! 30x") # 265252859812191058636308480000000, a plain int
jay.j("1r2 + 1r3") # Fraction(5, 6)
Seeing what an expression became
A compiled expression is not the string you wrote. +/ % # is a fork;
+/ w * x is one blockwise kernel with the sum folded into it. explain
prints that structure, one section per sentence:
k = jay.j.compile("+/ {w} * {x}", {"w": [1.0, 2.0, 3.0]})
print(k.explain({"x": [4.0, 5.0, 6.0]}))
source:
+/ {w} * {x}
parameters: w, x
sentence 1 | +/ {w} * {x}
fused kernel (1 op: *; +/ absorbed; block 8192) → scalar float [kernel ran]
in 0:
{x} → 3 $ float
in 1:
{w} → 3 $ float
falls back to:
monad +/
...
Values follow the same cascade as a call — interpolated, bound, call-time.
With every parameter filled the program is run and each node is annotated
with the shape and dtype it produced, and each fused node with whether its
kernel ran or handed the work back to the chain, and why. With a parameter
missing, the structure is printed alone. libjay --explain -e '...' is the
same thing from the shell.
Device placement
Where an expression runs is separate from what it is bound to. bind gives
a kernel data; deploy gives it a processor. Both return a new kernel, and
neither changes the answer.
jay.devices()
# [Device(name='AMD Radeon Pro 560', backend='metal',
# kind='discrete GPU', f64=False),
# Device(name='Intel(R) HD Graphics 630', backend='metal',
# kind='integrated GPU', f64=False)]
k = jay.j.compile("+/ {w} * {x}").bind({"w": w, "x": x})
g = k.deploy("gpu")
g() # the same value, computed on the GPU
What reaches the GPU is the fused elementwise chains — the same blockwise
kernels explain shows, generated as shader code at run time. Everything
else runs on the CPU, and so does any chain the device cannot take;
explain says which and why (device: gpu, device: cpu (…)). Nothing
here is a separate build: the backend is in the ordinary wheel and is
dormant on a machine with no adapter.
Precision is not silently traded away. libjay computes floats in f64, and most adapters have no f64 in shaders at all — Metal has none. On those an f64 chain simply stays on the CPU. Single precision is available by asking for it:
g = k.deploy("gpu", precision="f32") # yes, I want f32
Data can stay where it is computed. upload returns a value that
carries its own location, so calling a kernel repeatedly over it uploads
nothing after the first time:
g = jay.j.compile("+/ {w} * {x}").deploy("gpu")
pinned = g.bind({"w": g.upload(w), "x": g.upload(x)})
pinned() # no upload
The one-call shortcut jay.j("...") has no device: there is nowhere in one
call to say where, and uploading data for a single run rarely pays for
itself.
The CLI
libjay -e '(+/ % #) 3 1 4 1 5' # 2.8
libjay -e "⎕←'Hello, world!'" --lang apl # APL
libjay examples/hello.apl # a file; the extension
# picks the language
libjay --explain -e '+/ {w} * {x}' # the structure, not a result
.ijs/.j are J, .apl is APL; --lang overrides. -e defaults to J.
More
- Runnable examples — the glyphs are already in the files, no APL keyboard needed.
- Language coverage — what each frontend understands today.
- Benchmarks — against Polars, numba and numpy.
- The Rust and C surfaces of the same engine, and the source.
MIT licensed.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file libjay-0.1.0.tar.gz.
File metadata
- Download URL: libjay-0.1.0.tar.gz
- Upload date:
- Size: 295.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d6fce3fe792b1664b7f0a53b609160ad90fdd6c24925c066596777c6aa3d1656
|
|
| MD5 |
a03ffe5b7a3f8ab7ec567f6a69de78ac
|
|
| BLAKE2b-256 |
bb453e68542f35e6340d8f379148a18a128f76838c866c385f9b0812c72de424
|
Provenance
The following attestation bundles were made for libjay-0.1.0.tar.gz:
Publisher:
publish.yml on amyodov/libjay
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
libjay-0.1.0.tar.gz -
Subject digest:
d6fce3fe792b1664b7f0a53b609160ad90fdd6c24925c066596777c6aa3d1656 - Sigstore transparency entry: 2545240387
- Sigstore integration time:
-
Permalink:
amyodov/libjay@1f2fa376889df715ad59eb4a79f14daed725bd07 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/amyodov
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1f2fa376889df715ad59eb4a79f14daed725bd07 -
Trigger Event:
release
-
Statement type:
File details
Details for the file libjay-0.1.0-cp310-abi3-win_amd64.whl.
File metadata
- Download URL: libjay-0.1.0-cp310-abi3-win_amd64.whl
- Upload date:
- Size: 3.8 MB
- Tags: CPython 3.10+, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c786357913ca4dfc25bffbbe4fe74622b557df3200c20cad92e6c7041d355b05
|
|
| MD5 |
4626d64205225b639428eb705b78fec5
|
|
| BLAKE2b-256 |
59e0a442941bf593937b6c66a4e50c53f173528ef2b163ebefe86ba457cb7a54
|
Provenance
The following attestation bundles were made for libjay-0.1.0-cp310-abi3-win_amd64.whl:
Publisher:
publish.yml on amyodov/libjay
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
libjay-0.1.0-cp310-abi3-win_amd64.whl -
Subject digest:
c786357913ca4dfc25bffbbe4fe74622b557df3200c20cad92e6c7041d355b05 - Sigstore transparency entry: 2545240697
- Sigstore integration time:
-
Permalink:
amyodov/libjay@1f2fa376889df715ad59eb4a79f14daed725bd07 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/amyodov
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1f2fa376889df715ad59eb4a79f14daed725bd07 -
Trigger Event:
release
-
Statement type:
File details
Details for the file libjay-0.1.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: libjay-0.1.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 3.9 MB
- Tags: CPython 3.10+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e6ca8697d537740c5b8d03b84ebb05c9d65d2eb3af4e49c75861a189b97cff2e
|
|
| MD5 |
02ceab2ce261bfcbf8b311906a34be1f
|
|
| BLAKE2b-256 |
9b14a754c1c3af94289128bf3b77154e6e2143544af45cfcd82b43634fd5026f
|
Provenance
The following attestation bundles were made for libjay-0.1.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
publish.yml on amyodov/libjay
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
libjay-0.1.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
e6ca8697d537740c5b8d03b84ebb05c9d65d2eb3af4e49c75861a189b97cff2e - Sigstore transparency entry: 2545240757
- Sigstore integration time:
-
Permalink:
amyodov/libjay@1f2fa376889df715ad59eb4a79f14daed725bd07 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/amyodov
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1f2fa376889df715ad59eb4a79f14daed725bd07 -
Trigger Event:
release
-
Statement type:
File details
Details for the file libjay-0.1.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: libjay-0.1.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 3.9 MB
- Tags: CPython 3.10+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2ee2ab4af2e87c9bce7237bd9026cef7f9a83f2e25ca4551be0243c0b8270410
|
|
| MD5 |
03dbd92e67cc11f51f32327f5b605a27
|
|
| BLAKE2b-256 |
559e5a25507002f4ea8c14ae8a19e90bb8757d545023dbb63028580d9f0ccad3
|
Provenance
The following attestation bundles were made for libjay-0.1.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:
Publisher:
publish.yml on amyodov/libjay
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
libjay-0.1.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -
Subject digest:
2ee2ab4af2e87c9bce7237bd9026cef7f9a83f2e25ca4551be0243c0b8270410 - Sigstore transparency entry: 2545240430
- Sigstore integration time:
-
Permalink:
amyodov/libjay@1f2fa376889df715ad59eb4a79f14daed725bd07 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/amyodov
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1f2fa376889df715ad59eb4a79f14daed725bd07 -
Trigger Event:
release
-
Statement type:
File details
Details for the file libjay-0.1.0-cp310-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: libjay-0.1.0-cp310-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 3.4 MB
- Tags: CPython 3.10+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9945859597116b9d16e759251436fa048b915a489206c4c606f33a3b62611980
|
|
| MD5 |
99745ef735ff012509b74bf9b04aee1a
|
|
| BLAKE2b-256 |
7329023be52053fe518b3fe4459ffdb478d2547ae7993e620175db2ec05d99ce
|
Provenance
The following attestation bundles were made for libjay-0.1.0-cp310-abi3-macosx_11_0_arm64.whl:
Publisher:
publish.yml on amyodov/libjay
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
libjay-0.1.0-cp310-abi3-macosx_11_0_arm64.whl -
Subject digest:
9945859597116b9d16e759251436fa048b915a489206c4c606f33a3b62611980 - Sigstore transparency entry: 2545240608
- Sigstore integration time:
-
Permalink:
amyodov/libjay@1f2fa376889df715ad59eb4a79f14daed725bd07 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/amyodov
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1f2fa376889df715ad59eb4a79f14daed725bd07 -
Trigger Event:
release
-
Statement type:
File details
Details for the file libjay-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl.
File metadata
- Download URL: libjay-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl
- Upload date:
- Size: 3.6 MB
- Tags: CPython 3.10+, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3ed0dabb3e7457eff449b4db8f4a4c270d3f8fbdf13dedf73529ac1fdff794cb
|
|
| MD5 |
f7ade351617cadce30221f26923cbea2
|
|
| BLAKE2b-256 |
1bacd13ce024439d25adb8d1349996eb3a54d37f61f1b79bf5576e2d195b1045
|
Provenance
The following attestation bundles were made for libjay-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl:
Publisher:
publish.yml on amyodov/libjay
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
libjay-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl -
Subject digest:
3ed0dabb3e7457eff449b4db8f4a4c270d3f8fbdf13dedf73529ac1fdff794cb - Sigstore transparency entry: 2545240515
- Sigstore integration time:
-
Permalink:
amyodov/libjay@1f2fa376889df715ad59eb4a79f14daed725bd07 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/amyodov
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1f2fa376889df715ad59eb4a79f14daed725bd07 -
Trigger Event:
release
-
Statement type: