#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DigitOrder {
RowMajor,
Morton,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PadPolicy {
MixedRadix,
Pow2,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Layout {
pub dims: Vec<usize>,
pub padded: Vec<usize>,
pub modes: Vec<usize>,
pub strides: Vec<usize>,
pub order: DigitOrder,
pub pad: PadPolicy,
}
fn smooth_factors(mut p: usize) -> Option<Vec<usize>> {
let mut f = Vec::new();
for q in [7usize, 5, 3, 2] {
while p.is_multiple_of(q) {
f.push(q);
p /= q;
}
}
(p == 1).then_some(f)
}
pub fn padded_len(n: usize, pad: PadPolicy) -> (usize, Vec<usize>) {
assert!(n >= 1);
match pad {
PadPolicy::Pow2 => {
let p = n.next_power_of_two();
(p, vec![2; p.trailing_zeros() as usize])
}
PadPolicy::MixedRadix => {
let mut p = n;
loop {
if let Some(f) = smooth_factors(p) {
return (p, f);
}
p += 1;
}
}
}
}
impl Layout {
pub fn new(dims: &[usize], order: DigitOrder, pad: PadPolicy) -> Self {
assert!(!dims.is_empty(), "Layout: empty shape");
let mut padded = Vec::new();
let mut digits: Vec<Vec<usize>> = Vec::new();
for &n in dims {
let (p, f) = padded_len(n, pad);
padded.push(p);
digits.push(f);
}
let mut axis_stride = vec![1usize; dims.len()];
for a in (0..dims.len().saturating_sub(1)).rev() {
axis_stride[a] = axis_stride[a + 1] * padded[a + 1];
}
let digit_weight = |a: usize, j: usize| -> usize { digits[a][j + 1..].iter().product() };
let mut modes = Vec::new();
let mut strides = Vec::new();
match order {
DigitOrder::RowMajor => {
for a in 0..dims.len() {
for j in 0..digits[a].len() {
modes.push(digits[a][j]);
strides.push(digit_weight(a, j) * axis_stride[a]);
}
}
}
DigitOrder::Morton => {
let lmax = digits.iter().map(|d| d.len()).max().unwrap_or(0);
for level in (1..=lmax).rev() {
for a in 0..dims.len() {
let l = digits[a].len();
if l >= level {
let j = l - level;
modes.push(digits[a][j]);
strides.push(digit_weight(a, j) * axis_stride[a]);
}
}
}
}
}
if modes.is_empty() {
modes.push(1);
strides.push(1);
}
Self {
dims: dims.to_vec(),
padded,
modes,
strides,
order,
pad,
}
}
pub fn len(&self) -> usize {
self.dims.iter().product()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn tensor_len(&self) -> usize {
self.modes.iter().product()
}
pub fn pad_grid(&self, data: &[f64]) -> Vec<f64> {
assert_eq!(data.len(), self.len());
if self.dims == self.padded {
return data.to_vec();
}
let nd = self.dims.len();
let total: usize = self.padded.iter().product();
let mut src_stride = vec![1usize; nd];
for a in (0..nd.saturating_sub(1)).rev() {
src_stride[a] = src_stride[a + 1] * self.dims[a + 1];
}
let mut out = Vec::with_capacity(total);
let mut idx = vec![0usize; nd];
for _ in 0..total {
let mut off = 0;
for a in 0..nd {
off += reflect(idx[a], self.dims[a]) * src_stride[a];
}
out.push(data[off]);
for a in (0..nd).rev() {
idx[a] += 1;
if idx[a] < self.padded[a] {
break;
}
idx[a] = 0;
}
}
out
}
pub fn to_tensor(&self, padded: &[f64]) -> Vec<f64> {
let n = self.tensor_len();
assert_eq!(padded.len(), n);
if self.is_identity() {
return padded.to_vec();
}
let mut out = Vec::with_capacity(n);
self.odometer(|src| out.push(padded[src]));
out
}
pub fn from_tensor(&self, tensor: &[f64]) -> Vec<f64> {
let n = self.tensor_len();
assert_eq!(tensor.len(), n);
let grid = if self.is_identity() {
tensor.to_vec()
} else {
let mut g = vec![0.0; n];
let mut t = 0;
self.odometer(|src| {
g[src] = tensor[t];
t += 1;
});
g
};
self.crop(&grid)
}
fn crop(&self, grid: &[f64]) -> Vec<f64> {
if self.dims == self.padded {
return grid.to_vec();
}
let nd = self.dims.len();
let mut pstride = vec![1usize; nd];
for a in (0..nd.saturating_sub(1)).rev() {
pstride[a] = pstride[a + 1] * self.padded[a + 1];
}
let mut out = Vec::with_capacity(self.len());
let mut idx = vec![0usize; nd];
for _ in 0..self.len() {
let off: usize = (0..nd).map(|a| idx[a] * pstride[a]).sum();
out.push(grid[off]);
for a in (0..nd).rev() {
idx[a] += 1;
if idx[a] < self.dims[a] {
break;
}
idx[a] = 0;
}
}
out
}
fn is_identity(&self) -> bool {
let mut s = 1usize;
for k in (0..self.modes.len()).rev() {
if self.strides[k] != s {
return false;
}
s *= self.modes[k];
}
true
}
fn odometer(&self, mut f: impl FnMut(usize)) {
let d = self.modes.len();
let n = self.tensor_len();
let mut digit = vec![0usize; d];
let mut off = 0usize;
for _ in 0..n {
f(off);
let mut k = d;
while k > 0 {
k -= 1;
digit[k] += 1;
off += self.strides[k];
if digit[k] < self.modes[k] {
break;
}
off -= self.strides[k] * self.modes[k];
digit[k] = 0;
}
}
}
}
fn reflect(i: usize, n: usize) -> usize {
if n == 1 {
return 0;
}
let period = 2 * (n - 1);
let r = i % period;
if r < n {
r
} else {
period - r
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rowmajor_pow2_is_identity() {
let l = Layout::new(&[8, 4], DigitOrder::RowMajor, PadPolicy::Pow2);
assert_eq!(l.modes, vec![2; 5]);
let x: Vec<f64> = (0..32).map(|i| i as f64).collect();
assert_eq!(l.to_tensor(&l.pad_grid(&x)), x);
}
#[test]
fn morton_matches_bit_interleave() {
let l = Layout::new(&[4, 4], DigitOrder::Morton, PadPolicy::Pow2);
let x: Vec<f64> = (0..16).map(|i| i as f64).collect();
let t = l.to_tensor(&x);
for (ti, &v) in t.iter().enumerate() {
let y = ((ti >> 3) & 1) << 1 | ((ti >> 1) & 1);
let xx = ((ti >> 2) & 1) << 1 | (ti & 1);
assert_eq!(v, (y * 4 + xx) as f64);
}
}
#[test]
fn roundtrip_all_policies() {
for dims in [
vec![1000],
vec![12, 20],
vec![5, 6, 7],
vec![11, 3, 9],
vec![1],
] {
for order in [DigitOrder::RowMajor, DigitOrder::Morton] {
for pad in [PadPolicy::MixedRadix, PadPolicy::Pow2] {
let l = Layout::new(&dims, order, pad);
let n = l.len();
let x: Vec<f64> = (0..n).map(|i| (i as f64).sin()).collect();
let t = l.to_tensor(&l.pad_grid(&x));
assert_eq!(t.len(), l.tensor_len());
assert_eq!(l.from_tensor(&t), x, "{dims:?} {order:?} {pad:?}");
}
}
}
}
#[test]
fn mixed_radix_lengths() {
assert_eq!(padded_len(384, PadPolicy::MixedRadix).0, 384);
assert_eq!(padded_len(384, PadPolicy::Pow2).0, 512);
assert_eq!(
padded_len(500, PadPolicy::MixedRadix).1,
vec![5, 5, 5, 2, 2]
);
assert_eq!(padded_len(11, PadPolicy::MixedRadix).0, 12);
assert_eq!(reflect(5, 4), 1);
}
}