^ ^ ^ ^ =(o.o)= ~byakuren =(o.o)= m m m m
A bridge between the J programming language and Futhark, enabling high-performance parallel array computations from J.
This library allows you to write Futhark code directly in your J source files and execute it with full parallel performance. Futhark code is compiled to native shared libraries and called via J’s FFI, providing seamless interoperability between J’s expressive array notation and Futhark’s parallel execution model.
0 : 0 blocksfut is called, not on first usex verb yrequire 'dll')# Using Homebrew (macOS/Linux)
brew install futhark
# Using Stack (Haskell)
stack install futhark
# FreeBSD
pkg install hs-futharkNB. Load the library
load 'path/to/futhark.ijs'
NB. Define a Futhark function
double =: 'entry f (xs: []f64) : []f64 = map (*2) xs' fut
NB. Use it like any J verb
double 1 2 3 4 5
2 4 6 8 10
NB. Scalar reductions work too
total =: 'entry f (xs: []f64) : f64 = reduce (+) 0 xs' fut
total 1 2 3 4 5
15
NB. Dyadic verbs (two inputs) work naturally
dot =: 'entry f (xs: []f64) (ys: []f64) : f64 = reduce (+) 0 (map2 (*) xs ys)' fut
(1 2 3) dot (4 5 6)
32
NB. Scalar inputs work too (v1.6.0)
factors =: 0 : 0
entry f (n: i64) : []i64 =
let (_, _, result) =
loop (x, i, acc) = (n, 2i64, ([] : []i64))
while x > 1 do
if x % i == 0 then (x / i, i, acc ++ [i])
else (x, i + 1, acc)
in result
) fut
factors 12
2 2 3The fut adverb takes a Futhark source string and returns
a J verb:
NB. Single-line syntax for simple functions
my_verb =: 'futhark_source_code' fut
result =: my_verb input_array
NB. Multiline syntax using J's 0 : 0 for complex functions
NB. Note: 0 : 0 creates a string, then apply fut separately
my_src =: 0 : 0
entry f (xs: []f64) : []f64 =
let result = map (*2) xs
in result
)
my_verb =: my_src futFuthark functions must be declared as entry points:
entry <name> (<param>: <type>) : <return_type> = <body>
Examples:
-- Array transformation (returns []f64)
entry f (xs: []f64) : []f64 = map (*2) xs
-- Scalar reduction (returns f64)
entry f (xs: []f64) : f64 = reduce (+) 0 xs
-- Using lambda expressions
entry f (xs: []f64) : []f64 = map (\x -> x * x + 1) xs
square =: 'entry f (xs: []f64) : []f64 = map (\x -> x * x) xs' fut
negate =: 'entry f (xs: []f64) : []f64 = map (\x -> 0 - x) xs' futsum =: 'entry f (xs: []f64) : f64 = reduce (+) 0 xs' fut
product =: 'entry f (xs: []f64) : f64 = reduce (*) 1 xs' fut
minimum =: 'entry f (xs: []f64) : f64 = reduce f64.min f64.highest xs' fut
maximum =: 'entry f (xs: []f64) : f64 = reduce f64.max f64.lowest xs' futcumsum =: 'entry f (xs: []f64) : []f64 = scan (+) 0 xs' fut
runmax =: 'entry f (xs: []f64) : []f64 = scan f64.max f64.lowest xs' futNB. Mean
mean =: 'entry f (xs: []f64) : f64 = reduce (+) 0 xs / f64.i64 (length xs)' fut
NB. Variance
variance =: 'entry f (xs: []f64) : f64 = let mean = reduce (+) 0 xs / f64.i64 (length xs) in let sq_diffs = map (\x -> (x - mean) ** 2) xs in reduce (+) 0 sq_diffs / f64.i64 (length xs)' fut
NB. Standard deviation
stddev =: 'entry f (xs: []f64) : f64 = let mean = reduce (+) 0 xs / f64.i64 (length xs) in let sq_diffs = map (\x -> (x - mean) ** 2) xs in f64.sqrt (reduce (+) 0 sq_diffs / f64.i64 (length xs))' fut
NB. Min-max normalization
normalize =: 'entry f (xs: []f64) : []f64 = let min_val = reduce f64.min f64.highest xs in let max_val = reduce f64.max f64.lowest xs in map (\x -> (x - min_val) / (max_val - min_val)) xs' fut
NB. Softmax
softmax =: 'entry f (xs: []f64) : []f64 = let max_val = reduce f64.max f64.lowest xs in let exps = map (\x -> f64.exp (x - max_val)) xs in let sum_exps = reduce (+) 0 exps in map (\e -> e / sum_exps) exps' futFuthark functions with two input parameters become dyadic J verbs:
NB. Dot product: x dot y
dot =: 'entry f (xs: []f64) (ys: []f64) : f64 = reduce (+) 0 (map2 (*) xs ys)' fut
(1 2 3 4 5) dot (6 7 8 9 10) NB. Returns 130
NB. Element-wise operations
mul =: 'entry f (xs: []f64) (ys: []f64) : []f64 = map2 (*) xs ys' fut
add =: 'entry f (xs: []f64) (ys: []f64) : []f64 = map2 (+) xs ys' fut
sub =: 'entry f (xs: []f64) (ys: []f64) : []f64 = map2 (-) xs ys' fut
NB. Element-wise max/min
max2 =: 'entry f (xs: []f64) (ys: []f64) : []f64 = map2 f64.max xs ys' fut
min2 =: 'entry f (xs: []f64) (ys: []f64) : []f64 = map2 f64.min xs ys' fut
NB. Weighted sum
weighted_sum =: 'entry f (ws: []f64) (xs: []f64) : f64 = reduce (+) 0 (map2 (*) ws xs)' futNB. Transpose a matrix
transp =: 'entry f (xs: [][]f64) : [][]f64 = transpose xs' fut
mat =: 3 4 $ i. 12
transp mat NB. Returns 4 3 matrix (matches J |: mat)
NB. Flatten 2D to 1D
flat =: 'entry f (xs: [][]f64) : []f64 = flatten xs' fut
flat mat NB. Returns 1D array of 12 elements (matches J , mat)NB. 3D arrays work seamlessly
arr3d =: 2 3 4 $ i. 24
NB. 3D identity
id3d =: 'entry f (xs: [][][]f64) : [][][]f64 = xs' fut
id3d arr3d NB. Returns same 2x3x4 array
NB. 3D element-wise operations
map3d =: 'entry f (xs: [][][]f64) : [][][]f64 = map (map (map (*2))) xs' fut
map3d arr3d NB. Returns 2x3x4 array with doubled values (matches J 2*arr3d)
NB. Flatten 3D to 1D
flat3d =: 'entry f (xs: [][][]f64) : []f64 = flatten (flatten xs)' fut
flat3d arr3d NB. Returns 24-element 1D array (matches J , arr3d)
NB. Sum over 3D array
sum3d =: 'entry f (xs: [][][]f64) : f64 = flatten (flatten xs) |> reduce (+) 0' fut
sum3d arr3d NB. Returns scalar sum (matches J +/, arr3d)Futhark functions can take scalar inputs instead of arrays:
NB. Prime factorization: scalar in, array out
factors =: 0 : 0
entry f (n: i64) : []i64 =
let (_, _, result) =
loop (x, i, acc) = (n, 2i64, ([] : []i64))
while x > 1 do
if x % i == 0 then (x / i, i, acc ++ [i])
else (x, i + 1, acc)
in result
) fut
factors 12 NB. Returns 2 2 3
factors 100 NB. Returns 2 2 5 5
NB. Fibonacci: scalar in, scalar out
fib =: 'entry f (n: i64) : i64 = loop (a, b, i) = (0i64, 1i64, 0i64) while i < n do (b, a + b, i + 1) |> (.0)' fut
fib 10 NB. Returns 55
NB. Check if prime: scalar in, scalar out (bool as i64)
is_prime =: 'entry f (n: i64) : i64 = if n < 2 then 0 else if n == 2 then 1 else if n % 2 == 0 then 0 else loop (i, result) = (3i64, 1i64) while i * i <= n && result == 1 do (i + 2, if n % i == 0 then 0 else 1) |> (.1)' futFuthark supports fixed-size inner dimensions using [n]
syntax. These are fully supported:
NB. 1D input to 2D output with fixed inner dimension
triple =: 'entry f (ns: []i64) : [][3]i64 = map (\n -> [n, n*2, n*3]) ns' fut
triple 1 2 3 4 5
1 2 3
2 4 6
3 6 9
4 8 12
5 10 15
NB. Batch factorization: factor many numbers in parallel
NB. Uses helper function 'def' - parser correctly finds entry point
batch_factor =: 0 : 0
def factorize (n: i64) : [32]i64 =
let (_, _, factors, _) =
loop (x, i, acc, c) = (n, 2i64, replicate 32 0i64, 0i64)
while x > 1 && c < 32 do
if x % i == 0
then (x / i, i, acc with [c] = i, c + 1)
else (x, i + 1, acc, c)
in factors
entry f (ns: []i64) : [][32]i64 =
map factorize ns
) fut
NB. Factor multiple numbers at once - runs in parallel!
batch_factor 12 100 30
2 2 3 0 0 ...
2 2 5 5 0 ...
2 3 5 0 0 ...Note: v1.7.0 fixes parsing so helper functions (def)
don’t interfere with entry point detection.
NB. Take first n elements
take5 =: 'entry f (xs: []f64) : []f64 = take 5 xs' fut
take5 i. 10 NB. Returns 0 1 2 3 4
NB. Drop first n elements
drop3 =: 'entry f (xs: []f64) : []f64 = drop 3 xs' fut
drop3 i. 10 NB. Returns 3 4 5 6 7 8 9
NB. Replicate each element
rep2 =: 'entry f (xs: []f64) : []f64 = flatten (map (\x -> replicate 2 x) xs)' fut
rep2 1 2 3 NB. Returns 1 1 2 2 3 3
NB. Concatenate array with itself
concat_self =: 'entry f (xs: []f64) : []f64 = concat xs xs' fut
concat_self 1 2 3 NB. Returns 1 2 3 1 2 3| Futhark | J FFI | Description |
|---|---|---|
f64 |
d |
64-bit float (double) |
f32 |
f |
32-bit float |
i64 |
x |
64-bit signed integer |
i32 |
i |
32-bit signed integer |
i16 |
s |
16-bit signed integer |
i8 |
c |
8-bit signed integer |
u64 |
x |
64-bit unsigned integer |
u32 |
i |
32-bit unsigned integer |
NB. Cache directory (default: /tmp/futhark_j/)
FUTHARK_CACHE =: '/path/to/cache/'
NB. Backend: 'c' (sequential), 'multicore' (parallel), 'ispc'
FUTHARK_BACKEND =: 'multicore'
NB. Thread count for parallel backends
FUTHARK_THREADS =: 12NB. Clear compiled library cache
fut_clear_cache ''
NB. Change thread count
fut_set_threads 8
NB. Display version info
fut_version ''Typical results for standard deviation on different array sizes:
| Size | Futhark | J | Speedup |
|---|---|---|---|
| 100K | 1.7ms | 0.4ms | 0.2x |
| 500K | 3.2ms | 2.8ms | 0.9x |
| 1M | 11ms | 14ms | 1.3x |
| 2M | 40ms | 31ms | 0.8x |
Results vary by operation complexity and system configuration.
Futhark operations are semantically equivalent to J verbs:
| Futhark | J | Description |
|---|---|---|
reduce (+) 0 xs |
+/ xs |
Sum |
reduce (*) 1 xs |
*/ xs |
Product |
map (*2) xs |
2 * xs |
Double |
map (\x -> x*x) xs |
*: xs |
Square |
scan (+) 0 xs |
+/\ xs |
Running sum |
transpose xs |
\|: xs |
Transpose |
flatten xs |
, xs |
Ravel |
map2 (*) xs ys |
xs * ys |
Element-wise multiply |
map2 (+) xs ys |
xs + ys |
Element-wise add |
reduce (+) 0 (map2 (*) xs ys) |
xs +/@:* ys |
Dot product |
The test suite verifies all operations match their J equivalents.
| Version | Changes |
|---|---|
| 1.7.0 | Fixed-size array dimensions ([][32]i64), correct
parsing with def helper functions, 123 tests |
| 1.6.0 | Scalar input support, improved error reporting with full compiler output, 96 tests |
| 1.5.0 | Use J’s built-in SHA-256 (128!:6) for cache key hashing instead of polynomial hash |
| 1.4.0 | Immediate compilation at definition time, persistent Futhark context (no warmup needed) |
| 1.3.0 | Dyadic verb support (two inputs), 82 tests |
| 1.2.0 | Dynamic n-dimensional array support, unified executor, 73 tests |
| 1.1.0 | 2D array support, output shape queries, 44 tests |
| 1.0.0 | Initial release, 1D arrays, scalar/array returns, 30 tests |
futhark.ijs - Main library (load this)
futhark_test.ijs - Test suite (123 tests)
dot_product_bench.ijs - Dot product benchmark
stddev_bench.ijs - Standard deviation benchmark
compute_bench.ijs - Compute-intensive benchmarks (logistic map, Mandelbrot)
futhark_demo.ijs - Interactive demonstration
README.md - This documentation
DOCUMENTATION.md - Technical deep-dive
MIT License