Skip to content

Commit da4fa2f

Browse files
committed
update README for clarity and completeness; add installation instructions and usage examples
1 parent 2fc8d72 commit da4fa2f

4 files changed

Lines changed: 261 additions & 272 deletions

File tree

README.md

Lines changed: 260 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,277 @@
11
# ferrix
22

3-
A fast, memory-safe tensor library in Rust exposed as a Python package.
3+
A fast, memory-safe tensor library implemented in Rust and exposed as a Python package.
44

5-
## Install
5+
`ferrix` provides an `NDArray` core with stride-based indexing, vectorized math operations, slicing/reshaping utilities, and Python bindings via `pyo3`.
6+
7+
## Why ferrix
8+
9+
- Rust core for predictable performance and memory safety.
10+
- Python-friendly API for quick experimentation.
11+
- Multi-dimensional arrays with row-major strides.
12+
- Useful tensor operations for ML and numerical workloads.
13+
14+
## Installation
15+
16+
Install from PyPI:
617

718
```bash
8-
pip install ferrix
19+
python -m pip install ferrix
920
```
1021

11-
## Usage
22+
## Quick Start
1223

1324
```python
1425
import ferrix
1526

16-
a = ferrix.PyNDArray([1.0,2.0,3.0,4.0], [2,2])
17-
b = ferrix.PyNDArray([5.0,6.0,7.0,8.0], [2,2])
27+
# Create two 2x2 arrays
28+
a = ferrix.PyNDArray([1.0, 2.0, 3.0, 4.0], [2, 2])
29+
b = ferrix.PyNDArray([5.0, 6.0, 7.0, 8.0], [2, 2])
30+
31+
# Core arithmetic
32+
print(a.add(b).get([0, 0])) # 6.0
33+
print(a.mul(b).get([1, 1])) # 32.0
34+
print(a.scale(0.5).get([1, 0])) # 1.5
35+
36+
# Matrix multiplication
37+
print(a.matmul(b).get([0, 0])) # 19.0
38+
print(a.matmul_blas(b).get([0, 0])) # Compatibility API; currently same as matmul
39+
40+
# Activations and reductions
41+
print(a.relu().get([0, 0]))
42+
print(a.softmax().sum())
43+
print(a.sum(), a.mean(), a.argmax())
44+
45+
# Shape transforms
46+
print(a.transpose().shape())
47+
print(a.reshape([4]).shape())
48+
```
49+
50+
## Complete API Reference (Python)
51+
52+
`ferrix` exposes two classes:
53+
54+
- `ferrix.PyNDArray` for numeric tensors (`f64`)
55+
- `ferrix.PyBoolArray` for boolean masks
56+
57+
### `PyNDArray`
58+
59+
Constructor:
60+
61+
- `PyNDArray(data: list[float], shape: list[int]) -> PyNDArray`
62+
63+
```python
64+
x = ferrix.PyNDArray([1.0, 2.0, 3.0, 4.0], [2, 2])
65+
```
66+
67+
Introspection and element access:
68+
69+
- `shape() -> list[int]`
70+
- `get(index: list[int]) -> float`
71+
72+
```python
73+
x = ferrix.PyNDArray([1.0, 2.0, 3.0, 4.0], [2, 2])
74+
print(x.shape()) # [2, 2]
75+
print(x.get([1, 0])) # 3.0
76+
```
77+
78+
Reductions:
79+
80+
- `sum() -> float`
81+
- `mean() -> float`
82+
- `argmax() -> int` (index in flattened row-major order)
1883

19-
c = a.matmul_blas(b)
20-
print(c.get([0,0])) # 19.0
84+
```python
85+
x = ferrix.PyNDArray([1.0, 5.0, 3.0, 4.0], [2, 2])
86+
print(x.sum()) # 13.0
87+
print(x.mean()) # 3.25
88+
print(x.argmax()) # 1
89+
```
90+
91+
Element-wise math:
92+
93+
- `add(other: PyNDArray) -> PyNDArray`
94+
- `mul(other: PyNDArray) -> PyNDArray`
95+
- `scale(scalar: float) -> PyNDArray`
2196

22-
print(a.relu().get([0,0]))
23-
print(a.softmax().sum()) # 1.0
97+
```python
98+
a = ferrix.PyNDArray([1.0, 2.0, 3.0, 4.0], [2, 2])
99+
b = ferrix.PyNDArray([5.0, 6.0, 7.0, 8.0], [2, 2])
100+
101+
print(a.add(b).get([0, 1])) # 8.0
102+
print(a.mul(b).get([1, 0])) # 21.0
103+
print(a.scale(10).get([1, 1])) # 40.0
24104
```
25105

26-
## Benchmarks
106+
Activation functions:
107+
108+
- `relu() -> PyNDArray`
109+
- `sigmoid() -> PyNDArray`
110+
- `softmax() -> PyNDArray`
111+
112+
```python
113+
x = ferrix.PyNDArray([-1.0, 0.0, 1.0], [1, 3])
114+
print(x.relu().get([0, 0]))
115+
print(x.sigmoid().get([0, 2]))
116+
print(x.softmax().sum())
117+
```
118+
119+
Matrix operations:
120+
121+
- `matmul(other: PyNDArray) -> PyNDArray`
122+
- `matmul_blas(other: PyNDArray) -> PyNDArray`
123+
124+
```python
125+
a = ferrix.PyNDArray([1.0, 2.0, 3.0, 4.0], [2, 2])
126+
b = ferrix.PyNDArray([5.0, 6.0, 7.0, 8.0], [2, 2])
127+
128+
print(a.matmul(b).get([0, 0]))
129+
print(a.matmul_blas(b).get([0, 0]))
130+
```
131+
132+
Note: `matmul_blas` is currently a compatibility API that uses the same backend behavior as `matmul`.
133+
134+
Reshape and transpose:
135+
136+
- `reshape(new_shape: list[int]) -> PyNDArray`
137+
- `transpose() -> PyNDArray` (2D transpose)
138+
139+
```python
140+
x = ferrix.PyNDArray([1.0, 2.0, 3.0, 4.0], [2, 2])
141+
print(x.reshape([4]).shape())
142+
print(x.transpose().shape())
143+
```
144+
145+
Slicing and indexing:
146+
147+
- `slice_row(row: int) -> PyNDArray` (2D)
148+
- `slice_col(col: int) -> PyNDArray` (2D)
149+
- `slice_range(axis: int, start: int, end: int) -> PyNDArray`
150+
- `fancy_index(indices: list[int]) -> PyNDArray` (1D input)
151+
- `gather(axis: int, indices: list[int]) -> PyNDArray`
152+
153+
```python
154+
m = ferrix.PyNDArray([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], [2, 3])
155+
v = ferrix.PyNDArray([10.0, 20.0, 30.0, 40.0], [4])
156+
157+
print(m.slice_row(1).shape())
158+
print(m.slice_col(2).shape())
159+
print(m.slice_range(1, 0, 2).shape())
160+
print(v.fancy_index([3, 1, 1]).shape())
161+
print(m.gather(1, [2, 0]).shape())
162+
```
163+
164+
Masking and conditional operations:
165+
166+
- `boolean_mask(mask: PyBoolArray) -> PyNDArray`
167+
- `masked_fill(mask: PyBoolArray, value: float) -> None` (in-place)
168+
- `where_(condition: PyBoolArray, other: PyNDArray) -> PyNDArray`
169+
170+
```python
171+
x = ferrix.PyNDArray([1.0, 2.0, 3.0, 4.0], [2, 2])
172+
y = ferrix.PyNDArray([9.0, 9.0, 9.0, 9.0], [2, 2])
173+
mask = ferrix.PyBoolArray([True, False, True, False], [2, 2])
174+
175+
print(x.boolean_mask(mask).shape())
176+
x.masked_fill(mask, -1.0)
177+
print(x.where_(mask, y).shape())
178+
```
179+
180+
Mutation and cumulative operations:
181+
182+
- `set_slice(axis: int, start: int, end: int, value: float) -> None` (in-place)
183+
- `cumsum() -> PyNDArray` (flattened cumulative sum)
184+
185+
```python
186+
x = ferrix.PyNDArray([1.0, 2.0, 3.0, 4.0], [2, 2])
187+
x.set_slice(0, 0, 1, 0.0)
188+
print(x.get([0, 1]))
189+
print(x.cumsum().shape())
190+
```
191+
192+
### `PyBoolArray`
193+
194+
Constructor and methods:
195+
196+
- `PyBoolArray(data: list[bool], shape: list[int]) -> PyBoolArray`
197+
- `shape() -> list[int]`
198+
199+
```python
200+
mask = ferrix.PyBoolArray([True, False, True, False], [2, 2])
201+
print(mask.shape())
202+
```
203+
204+
## Error Behavior
205+
206+
- Invalid shapes, indices, or axis values raise Python exceptions backed by Rust panics.
207+
- Most binary operations require shape compatibility.
208+
- `fancy_index` is for 1D arrays.
209+
- `transpose` and `slice_row`/`slice_col` require 2D arrays.
210+
211+
## Feature Notes
212+
213+
- Arrays are row-major and stride-aware.
214+
- Shape checks and index checks panic on invalid inputs in the Rust core.
215+
- `matmul_blas` is currently a compatibility method that falls back to the same implementation as `matmul`.
216+
- Parallel execution is used for selected element-wise operations through `rayon`.
217+
218+
## Benchmarks (Indicative)
219+
220+
The following numbers are from repository examples and should be treated as indicative (hardware and build mode dependent):
27221

28222
| Operation | ferrix | NumPy |
29-
|-----------|--------|-------|
30-
| matmul 512×512 (BLAS) | 6.5ms | 2.1ms |
31-
| relu 1M elements | 0.52ms | 0.76ms |
32-
33-
## Features
34-
35-
- N-dimensional arrays with stride-based indexing
36-
- Zero-copy slicing, reshape, transpose
37-
- Element-wise ops: add, mul, scale, relu, sigmoid, softmax
38-
- Matrix multiply via OpenBLAS FFI
39-
- Parallel ops via rayon
40-
- Boolean masking, fancy indexing, gather
223+
|---|---:|---:|
224+
| matmul 512x512 | 6.5 ms | 2.1 ms |
225+
| relu 1M elements | 0.52 ms | 0.76 ms |
226+
227+
## Build From Source (Optional)
228+
229+
This project uses `maturin` to build the Python extension from Rust.
230+
231+
### Prerequisites
232+
233+
- Rust toolchain (`cargo`, `rustc`)
234+
- Python `>=3.8`
235+
- `maturin`
236+
237+
### Local development build
238+
239+
```bash
240+
python -m pip install --upgrade pip maturin
241+
maturin develop
242+
```
243+
244+
After this, `import ferrix` uses your local build in the active virtual environment.
245+
246+
### Build wheel/sdist
247+
248+
```bash
249+
rm -rf dist
250+
maturin build --release --out dist
251+
maturin sdist --out dist
252+
```
253+
254+
## Release and Publishing
255+
256+
For a complete PyPI release runbook, see:
257+
258+
- `docs/pypi-guide.md`
259+
260+
It includes versioning rules, artifact validation, upload options, and post-release checks.
261+
262+
## Repository Layout
263+
264+
Key files and directories:
265+
266+
- `src/lib.rs`: Rust tensor core (`NDArray` and `NDArrayView`)
267+
- `src/python.rs`: Python bindings (`PyNDArray`, `PyBoolArray`)
268+
- `src/tests/`: Rust unit tests
269+
- `test_ferrix.py`: Python API tests
270+
- `pyproject.toml`: Python packaging metadata and build backend
271+
- `Cargo.toml`: Rust crate configuration
272+
273+
## Contributing
274+
275+
Contributions are welcome.
276+
277+
For contributions and release process details, start from `docs/pypi-guide.md` and open a PR with clear change notes.

docs/pypi-guide.md

Whitespace-only changes.

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ readme = "README.md"
77
requires-python = ">=3.8"
88
dependencies = [
99
"maturin>=1.12.6",
10-
"numpy>=1.24.4",
1110
]
1211

1312
[build-system]

0 commit comments

Comments
 (0)