Coming from Python #
This page maps Python scientific computing concepts to their Raven equivalents. It assumes you already know OCaml basics.
Library Mapping #
| Python | Raven | Notes |
|---|---|---|
| NumPy | Nx | N-dimensional arrays, broadcasting, linear algebra, FFT |
| JAX | Rune | Functional transformations: grad, jvp, vmap |
| PyTorch / Flax | Kaun | Layers, optimizers, training loops |
| HuggingFace Tokenizers | Brot | BPE, WordPiece, Unigram; HF-compatible |
| pandas / Polars | Talon | Typed tables and queries; CSV and Parquet |
| Matplotlib | Hugin | Figures from tensors, rendered to PNG, SVG and PDF |
| Jupyter + IPython | Quill | Interactive REPL and markdown notebooks |
Key Differences #
Explicit Types #
NumPy casts types silently. Nx does not.
# Python: silently upcasts int + float -> float
a = np.array([1, 2, 3])
b = a + 1.5 # works
(* OCaml: types must match *)
let a = Nx.create Nx.Int32 [|3|] [|1l; 2l; 3l|]
(* Nx.add a (Nx.scalar Nx.Float32 1.5) -- type error *)
(* Cast explicitly *)
let a_f = Nx.cast Nx.Float32 a
let b = Nx.add a_f (Nx.scalar Nx.Float32 1.5)
Array Literals #
NumPy uses Python lists. Nx uses OCaml arrays with [| |] syntax.
x = np.array([[1, 2], [3, 4]])
let x = Nx.create Nx.Float32 [|2; 2|] [|1.; 2.; 3.; 4.|]
Slicing #
NumPy uses [] with :. Nx uses the slice function with index constructors.
x[0:2, :] # first two rows
x[:, 1] # second column
x[::2] # every other element
Nx.slice [R (0, 2); A] x (* first two rows *)
Nx.slice [A; I 1] x (* second column *)
Nx.slice [S (0, -1, 2)] x (* every other element *)
No Separate Tensor Type #
In PyTorch, torch.Tensor is different from numpy.ndarray. In Raven, Rune operates directly on Nx.t values. There is no wrapper type.
# PyTorch: convert between types
x_np = np.array([1.0, 2.0])
x_torch = torch.from_numpy(x_np)
x_torch.requires_grad_(True)
(* Raven: just use Nx tensors directly *)
let x = Nx.create Nx.Float32 [|2|] [|1.0; 2.0|]
let gradient = Rune.grad' (fun x -> Nx.sum (Nx.mul x x)) x
Functional Transformations #
JAX users will find Rune familiar. PyTorch users: think of grad as a function transformer, not a method on tensors.
# JAX style
grad_fn = jax.grad(loss_fn)
grads = grad_fn(params)
# PyTorch style
loss = loss_fn(params)
loss.backward()
grads = params.grad
(* Rune: JAX-style functional transforms over your own typed record *)
let params_ptree = Nx.Ptree.instantiate (module Params)
let grads = Rune.grad params_ptree loss_fn params
(* Or compute value and gradient together *)
let loss, grads = Rune.value_and_grad params_ptree loss_fn params
Where JAX registers pytrees, Rune takes a structure: Params implements Nx.Ptree.S with one function, walk, over your record's fields, Nx.Ptree.instantiate makes it a value, and gradients come back with the same type as the parameters.
Module-Based Layers #
Kaun layers are plain records with init and apply functions, not classes with forward — and a model is a record you define, not a container object.
# PyTorch
class Model(nn.Module):
def __init__(self):
self.linear = nn.Linear(784, 10)
def forward(self, x):
return self.linear(x)
model = Model()
(* Kaun: a model is a typed record of layer records *)
type 'a model = { linear : 'a Kaun.Linear.t }
let apply p x = Kaun.Linear.apply p.linear x
let params = { linear = Kaun.Linear.init ~inputs:784 ~outputs:10 }
Parameters are plain data (records of Nx tensors), not hidden inside objects.
DataFrames #
pandas computes each operation as it is called, and finds a wrong column name when the line that uses it runs. Talon builds a query from typed column handles, checks every verb against its input's columns before reading any data, and computes the query when it runs.
# pandas
df["bmi"] = df["weight"] / df["height"] ** 2
(* Talon: a typed expression in a query *)
let weight = Talon.Col.float "weight" and height = Talon.Col.float "height"
let with_bmi df =
Talon.Query.(df |> derive Talon.Expr.[ "bmi" := weight /. (height *. height) ])
Detailed Comparisons #
Each library has a dedicated comparison page with side-by-side code examples: