ndslice vs NumPy — Side-by-Side
A practical comparison of D's mir ndslice and Python's NumPy. Every common NumPy pattern mapped to its ndslice equivalent, with notes on performance and differences.
If you know NumPy and want to use D's Mir for numerical computing, this guide maps the operations you already know. Both libraries share the same core concepts — N-dimensional arrays with strided memory, lazy views, broadcasting — but the syntax and idioms differ.
Setup
NumPy (Python)
import numpy as npndslice (D)
import mir.ndslice;
import mir.ndslice.topology;
import mir.algorithm.iteration;Creating arrays
| Operation | NumPy | ndslice |
|---|---|---|
| Zeros | np.zeros((3, 4)) | slice!double(3, 4) (zero-initialized) |
| From list | np.array([1,2,3,4]) | [1,2,3,4].sliced |
| Reshape from list | np.array([...]).reshape(2,3) | [...].sliced(2,3) |
| Integer range | np.arange(12) | iota(12) |
| 2-D range | np.arange(12).reshape(3,4) | iota(3,4) |
| Linspace | np.linspace(0,1,100) | linspace!double(0.0,1.0,100) |
| Ones | np.ones((3,4)) | slice!double(3,4) then s[] = 1.0 |
| Identity | np.eye(4) | slice!double(4,4) then fill diagonal |
Array properties
a = np.zeros((3, 4))
a.shape # (3, 4)
a.ndim # 2
a.size # 12
a.dtype # float64auto a = slice!double(3, 4);
a.shape // [3, 4]
a.shape.length // 2 (ndim)
a.elementCount // 12
// Type is part of the Slice template parameterIndexing
a[1, 2] # scalar
a[0] # first row
a[:, 2] # column 2
a[1:3, 1:4] # sub-matrix
a[-1] # last rowa[1, 2] // scalar
a[0] // first row (1-D slice)
a[0..$, 2] // column 2 (1-D slice)
a[1..3, 1..4] // sub-matrix (view)
a[$ - 1] // last rowReshaping and transposing
a.reshape(4, 3)
a.T # transpose
a.flatten() # 1-D copy
a.ravel() # 1-D view if possiblea.reshape(4, 3) // same element count required
a.transposed // zero-copy view
a.flattened // 1-D view (contiguous only)Key difference: a.T in NumPy is always a view; a.transposed in ndslice is also a view but changes the SliceKind to Universal, which carries strides. Some functions require Contiguous — use .as!double.slice to materialise.
Element-wise math
a * 2
a + b
np.sin(a)
a ** 2import mir.ndslice.topology : map;
import mir.math.common : sin;
a.map!(x => x * 2) // lazy
a.map!((x, y) => x + y) // doesn't work for two slices — use zip+map:
import mir.ndslice.topology : zip;
zip(a, b).map!(t => t[0] + t[1])
a.map!sin // element-wise sin
a.map!(x => x * x) // element-wise squareFor in-place operations:
a[] *= 2; // in-place scale
a[] = a.map!sin; // in-place sinReductions
a.sum()
a.sum(axis=0) # column sums
a.max()
a.min()
np.mean(a)import mir.algorithm.iteration : reduce;
import mir.math.stat : mean;
a.flattened.reduce!"a + b"(0.0) // total sum
// axis-wise: iterate manually or use topology
a.map!(x => x).each!... // no built-in axis reduce yet
a.flattened.reduce!fmax(double.nan) // max
a.flattened.reduce!fmin(double.nan) // min
a.mean // from mir.math.statBroadcasting
NumPy broadcasts automatically based on shape rules. ndslice requires explicit broadcasting via repeat or zip:
# NumPy: add row vector to each row of matrix
m + np.array([1, 2, 3, 4]) # auto-broadcast// ndslice: explicit repeat
import mir.ndslice.topology : repeat;
auto row = [1.0, 2.0, 3.0, 4.0].sliced;
auto tiled = row.repeat(3); // 3×4 lazy tiled view
zip(m, tiled).map!(t => t[0] + t[1])Linear algebra
np.dot(a, b)
np.linalg.inv(a)
np.linalg.solve(A, b)// via lubeck (kaleidicassociates) or mir-blas
import lubeck : mtimes, inv, solve;
auto C = mtimes(A, B); // matrix multiply
auto Ainv = inv(A);
auto x = solve(A, b);lubeck is a higher-level linear algebra layer on top of mir-blas and mir-lapack.
Performance comparison
| Aspect | NumPy | ndslice |
|---|---|---|
| Memory layout | C or Fortran order | Contiguous or strided |
| Lazy evaluation | No (eager) | Yes — topology operations are lazy |
| GC overhead | Python GC | Optional (use rcslice / @nogc) |
| SIMD | Via MKL/OpenBLAS | Auto-vectorized by compiler |
| Allocation | On every operation | Only on explicit slice!T(...) |
| Interop | C via ctypes | Direct pointer, zero overhead |
The biggest practical difference: ndslice lazy operations compose without intermediate allocations. m.transposed.map!(x => x * 2).flattened.reduce!"a+b"(0.0) allocates nothing — the entire pipeline is fused at compile time.
Tutorials
Practical guides for Mir and D numerical computing — ndslice vs NumPy, BetterC programming, setting up a D scientific computing project, and more.
ndslice Cookbook — Common Recipes for D Multidimensional Arrays
A practical, task-oriented cookbook for mir-algorithm's ndslice — create, reshape, slice, map, reduce, and fuse multidimensional arrays in D. Every example is compiled against current Mir source.