Libmir Archive

ndslice topology — Lazy Views

mir.ndslice.topology provides zero-copy transformations of ndslice: reshape, transposed, diagonal, zip, iota, repeat, and more. All views are lazy — no data is copied.

Old URL: Previously at mir-algorithm.libmir.org/mir_ndslice_topology.html. Redirects here.

mir.ndslice.topology contains the lazy view combinators — operations that return a new Slice pointing into the same memory (or generating values on demand) without copying data.

Import

import mir.ndslice.topology;
// or selectively:
import mir.ndslice.topology : iota, map, zip, transposed, reshape, flattened;

Views into existing data

transposed — swap axes

auto m = iota(3, 4);
auto t = m.transposed;   // 4×3, no copy
// m[i, j] == t[j, i]

For higher-dimensional slices, transposed accepts axis indices:

auto t3 = volume.transposed!(2, 0, 1);  // permute axes

flattened — collapse to 1-D

auto m = iota(3, 4);
auto v = m.flattened;    // length-12 1-D slice
// v[i] == m[i / 4, i % 4]

Works on contiguous slices only (use .assumeContiguous after a universal-stride slice if you know the data is actually contiguous).

reshape — change shape, same elements

auto m = iota(12).reshape(3, 4);
auto t = iota(3, 4).reshape(2, 6);

Fails to compile if the element count doesn't match.

diagonal — main diagonal as 1-D view

auto m = iota(4, 4);
auto d = m.diagonal;  // [0, 5, 10, 15] — no copy

Slicing sub-ranges

auto m = iota(5, 5);
auto block = m[1 .. 4, 1 .. 4];  // 3×3 sub-matrix (view)
auto row   = m[2];                 // row 2 as 1-D slice
auto col   = m[0 .. $, 2];        // column 2 as 1-D slice

Generating slices (no backing memory)

iota — integer sequence

auto v = iota(6);         // [0, 1, 2, 3, 4, 5]
auto m = iota(3, 4);      // [[0..3], [4..7], [8..11]]
auto m2 = iota([3, 4], 1); // start at 1 → [[1..4], [5..8], [9..12]]

iota is lazy — iterating it computes start + linear_index, never stores the values.

repeat — broadcast a scalar or slice

auto r = repeat(42.0, 3, 4);  // 3×4 slice, all 42.0 (lazy)
auto tiled = iota(1, 3).repeat(4);  // 4×1×3 — tile a row 4 times

linspace (from mir-algorithm extras)

import mir.math.numeric : linspace;
auto x = linspace!double(0.0, 1.0, 100);  // 100 evenly spaced values

Combining slices

zip — pair-wise combine

auto a = iota!double(5);
auto b = iota!double(5).map!(x => x * x);
auto pairs = zip(a, b);  // Slice of tuples (a[i], b[i])

pairs.each!((t) { writefln("%s → %s", t[0], t[1]); });

unzip — split a zipped slice

auto (left, right) = pairs.unzip;

cartesian — outer product of indices

auto grid = cartesian(iota(3), iota(4));  // 3×4 slice of (i, j) pairs

Element-wise transformations

map — lazy element transform

auto m = iota!double(3, 4);
auto scaled = m.map!(x => x * 0.1);   // lazy, no allocation
auto abs_m  = m.map!abs;              // using std.math.abs

map returns a slice of the same shape with a custom iterator — still a proper Slice, so all topology operations compose on it.

as — element type cast

auto ints = iota(3, 4);
auto doubles = ints.as!double;   // lazy cast, no copy

Combining topology operations

All topology operations compose freely because they all return Slice:

// Transpose, then flatten, then map
auto result = iota(4, 4)
    .transposed
    .flattened
    .map!(x => x * x);

// Sub-block of a 3-D tensor, then reduce
import mir.algorithm.iteration : reduce;
double sum = volume[1..3, 2..5, 0..4]
    .flattened
    .reduce!"a + b"(0.0);

On this page