#![allow(clippy::needless_range_loop, clippy::too_many_arguments)]
use super::aca::{BlackBoxMatrix, FnMatrix, Xorshift64, aca_partial_pivot};
use faer::Mat;
use rayon::prelude::*;
const NUM_NODES_3D: usize = 5;
const NUM_LEAVES_3D: usize = 3;
const ROOT_3D: usize = 4;
#[derive(Clone)]
pub enum HtNode3D {
Leaf {
dim: usize,
frame: Mat<f64>,
},
Interior {
left: usize,
right: usize,
transfer: Vec<f64>,
ranks: [usize; 3],
},
}
impl HtNode3D {
#[inline]
pub fn rank(&self) -> usize {
match self {
HtNode3D::Leaf { frame, .. } => frame.ncols(),
HtNode3D::Interior { ranks, .. } => ranks[0],
}
}
}
#[derive(Clone)]
pub struct HtTensor3D {
pub nodes: Vec<HtNode3D>,
pub shape: [usize; 3],
pub dx: [f64; 3],
}
impl HtTensor3D {
pub fn evaluate(&self, idx: [usize; 3]) -> f64 {
let u0 = self.leaf_vector(0, idx[0]);
let u1 = self.leaf_vector(1, idx[1]);
let u2 = self.leaf_vector(2, idx[2]);
let z3 = self.contract_interior(3, &u1, &u2);
let z4 = self.contract_interior(4, &u0, &z3);
z4[0]
}
#[inline]
fn leaf_vector(&self, node: usize, idx: usize) -> Vec<f64> {
match &self.nodes[node] {
HtNode3D::Leaf { frame, .. } => {
let k = frame.ncols();
(0..k).map(|j| frame[(idx, j)]).collect()
}
_ => {
debug_assert!(false, "Node {node} is not a leaf");
vec![]
}
}
}
#[inline]
fn contract_interior(&self, node: usize, left: &[f64], right: &[f64]) -> Vec<f64> {
match &self.nodes[node] {
HtNode3D::Interior {
transfer, ranks, ..
} => {
let [kt, kl, kr] = *ranks;
let mut result = vec![0.0; kt];
for t in 0..kt {
let mut sum = 0.0;
for l in 0..kl {
for r in 0..kr {
sum += transfer[t * kl * kr + l * kr + r] * left[l] * right[r];
}
}
result[t] = sum;
}
result
}
_ => {
debug_assert!(false, "Node {node} is not an interior node");
vec![]
}
}
}
pub fn from_dense(
data: &[f64],
shape: [usize; 3],
dx: [f64; 3],
tolerance: f64,
max_rank: usize,
) -> Self {
let [n0, n1, n2] = shape;
assert_eq!(data.len(), n0 * n1 * n2);
let eps_node = tolerance / (2.0_f64).sqrt();
let (u0, frame0) = mode_unfolding_svd(data, n0, n1 * n2, eps_node, max_rank);
let mut mode1 = vec![0.0; n1 * n0 * n2];
for i0 in 0..n0 {
for i1 in 0..n1 {
for i2 in 0..n2 {
mode1[i1 * (n0 * n2) + i0 * n2 + i2] = data[i0 * n1 * n2 + i1 * n2 + i2];
}
}
}
let (u1, frame1) = mode_unfolding_svd(&mode1, n1, n0 * n2, eps_node, max_rank);
let mut mode2 = vec![0.0; n2 * n0 * n1];
for i0 in 0..n0 {
for i1 in 0..n1 {
for i2 in 0..n2 {
mode2[i2 * (n0 * n1) + i0 * n1 + i1] = data[i0 * n1 * n2 + i1 * n2 + i2];
}
}
}
let (u2, frame2) = mode_unfolding_svd(&mode2, n2, n0 * n1, eps_node, max_rank);
let k0 = frame0.ncols();
let k1 = frame1.ncols();
let k2 = frame2.ncols();
let k12 = k1 * k2;
let mut mat12 = vec![0.0; n1 * n2 * n0];
for i0 in 0..n0 {
for i1 in 0..n1 {
for i2 in 0..n2 {
mat12[(i1 * n2 + i2) * n0 + i0] = data[i0 * n1 * n2 + i1 * n2 + i2];
}
}
}
let mut projected = vec![0.0; k12 * n0];
for l1 in 0..k1 {
for l2 in 0..k2 {
let row = l1 * k2 + l2;
for i0 in 0..n0 {
let mut sum = 0.0;
for i1 in 0..n1 {
for i2 in 0..n2 {
sum += frame1[(i1, l1)]
* frame2[(i2, l2)]
* mat12[(i1 * n2 + i2) * n0 + i0];
}
}
projected[row * n0 + i0] = sum;
}
}
}
let mut proj_mat: Mat<f64> = Mat::zeros(k12, n0);
for r in 0..k12 {
for c in 0..n0 {
proj_mat[(r, c)] = projected[r * n0 + c];
}
}
let svd3 = proj_mat.as_ref().thin_svd();
let (u3, sv3, _vt3) = match svd3 {
Ok(svd) => {
let s_col = svd.S().column_vector();
let sv: Vec<f64> = (0..s_col.nrows()).map(|i| s_col[i]).collect();
(svd.U().to_owned(), sv, svd.V().to_owned())
}
Err(_) => {
return Self::zero(shape, dx);
}
};
let k3 = truncation_rank(&sv3, eps_node)
.min(max_rank)
.min(u3.ncols())
.max(1);
let mut transfer3 = vec![0.0; k3 * k1 * k2];
for t in 0..k3 {
for l1 in 0..k1 {
for l2 in 0..k2 {
transfer3[t * k1 * k2 + l1 * k2 + l2] = u3[(l1 * k2 + l2, t)];
}
}
}
let mut m_tensor = vec![0.0; k0 * k1 * k2];
for i0 in 0..n0 {
for i1 in 0..n1 {
for i2 in 0..n2 {
let val = data[i0 * n1 * n2 + i1 * n2 + i2];
if val.abs() < 1e-30 {
continue;
}
for l0 in 0..k0 {
let u0_val = frame0[(i0, l0)];
for l1 in 0..k1 {
let u01_val = u0_val * frame1[(i1, l1)];
for l2 in 0..k2 {
m_tensor[l0 * k1 * k2 + l1 * k2 + l2] +=
u01_val * frame2[(i2, l2)] * val;
}
}
}
}
}
}
let mut root_mat = vec![0.0; k0 * k3];
for l0 in 0..k0 {
for t3 in 0..k3 {
let mut sum = 0.0;
for l1 in 0..k1 {
for l2 in 0..k2 {
sum += transfer3[t3 * k1 * k2 + l1 * k2 + l2]
* m_tensor[l0 * k1 * k2 + l1 * k2 + l2];
}
}
root_mat[l0 * k3 + t3] = sum;
}
}
let k_root = 1;
let mut transfer_root = vec![0.0; k0 * k3];
for l0 in 0..k0 {
for t3 in 0..k3 {
transfer_root[l0 * k3 + t3] = root_mat[l0 * k3 + t3];
}
}
let mut nodes = Vec::with_capacity(NUM_NODES_3D);
nodes.push(HtNode3D::Leaf {
dim: 0,
frame: frame0,
});
nodes.push(HtNode3D::Leaf {
dim: 1,
frame: frame1,
});
nodes.push(HtNode3D::Leaf {
dim: 2,
frame: frame2,
});
nodes.push(HtNode3D::Interior {
left: 1,
right: 2,
transfer: transfer3,
ranks: [k3, k1, k2],
});
nodes.push(HtNode3D::Interior {
left: 0,
right: 3,
transfer: transfer_root,
ranks: [k_root, k0, k3],
});
Self { nodes, shape, dx }
}
pub fn from_density(
density: &super::super::types::DensityField,
dx: [f64; 3],
tolerance: f64,
max_rank: usize,
) -> Self {
Self::from_dense(&density.data, density.shape, dx, tolerance, max_rank)
}
pub fn from_function_aca<F: Fn([usize; 3]) -> f64 + Sync>(
f: &F,
shape: [usize; 3],
dx: [f64; 3],
tolerance: f64,
max_rank: usize,
) -> Self {
let [n0, n1, n2] = shape;
let eps_node = tolerance / (2.0_f64).sqrt();
let n_samples = (8 * max_rank).min(n0 * n1).max(max_rank);
let mut rng = Xorshift64::new(42);
let mut fiber_mat0: Mat<f64> = Mat::zeros(n0, n_samples);
for s in 0..n_samples {
let i1 = rng.next_usize(n1);
let i2 = rng.next_usize(n2);
for i0 in 0..n0 {
fiber_mat0[(i0, s)] = f([i0, i1, i2]);
}
}
let frame0 = extract_frame_qr(&fiber_mat0, max_rank);
let mut fiber_mat1: Mat<f64> = Mat::zeros(n1, n_samples);
for s in 0..n_samples {
let i0 = rng.next_usize(n0);
let i2 = rng.next_usize(n2);
for i1 in 0..n1 {
fiber_mat1[(i1, s)] = f([i0, i1, i2]);
}
}
let frame1 = extract_frame_qr(&fiber_mat1, max_rank);
let mut fiber_mat2: Mat<f64> = Mat::zeros(n2, n_samples);
for s in 0..n_samples {
let i0 = rng.next_usize(n0);
let i1 = rng.next_usize(n1);
for i2 in 0..n2 {
fiber_mat2[(i2, s)] = f([i0, i1, i2]);
}
}
let frame2 = extract_frame_qr(&fiber_mat2, max_rank);
let k0 = frame0.ncols();
let k1 = frame1.ncols();
let k2 = frame2.ncols();
let k12 = k1 * k2;
let projected3_flat: Vec<f64> = (0..n0)
.into_par_iter()
.flat_map(|i0| {
let mut col = vec![0.0; k12];
for i1 in 0..n1 {
for i2 in 0..n2 {
let val = f([i0, i1, i2]);
if val.abs() < 1e-30 {
continue;
}
for l1 in 0..k1 {
for l2 in 0..k2 {
col[l1 * k2 + l2] += frame1[(i1, l1)] * frame2[(i2, l2)] * val;
}
}
}
}
col
})
.collect();
let mut projected3: Mat<f64> = Mat::zeros(k12, n0);
for i0 in 0..n0 {
for row in 0..k12 {
projected3[(row, i0)] = projected3_flat[i0 * k12 + row];
}
}
let svd3 = projected3.as_ref().thin_svd();
let (u3_mat, sv3, _vt3) = match svd3 {
Ok(svd) => {
let s_col = svd.S().column_vector();
let sv: Vec<f64> = (0..s_col.nrows()).map(|i| s_col[i]).collect();
(svd.U().to_owned(), sv, svd.V().to_owned())
}
Err(_) => {
return Self::zero(shape, dx);
}
};
let k3 = truncation_rank(&sv3, eps_node)
.min(max_rank)
.min(u3_mat.ncols())
.max(1);
let mut transfer3 = vec![0.0; k3 * k1 * k2];
for t in 0..k3 {
for l1 in 0..k1 {
for l2 in 0..k2 {
transfer3[t * k1 * k2 + l1 * k2 + l2] = u3_mat[(l1 * k2 + l2, t)];
}
}
}
let kk = k0 * k1 * k2;
let m_tensor: Vec<f64> = (0..n0)
.into_par_iter()
.fold(
|| vec![0.0; kk],
|mut acc, i0| {
for i1 in 0..n1 {
for i2 in 0..n2 {
let val = f([i0, i1, i2]);
if val.abs() < 1e-30 {
continue;
}
for l0 in 0..k0 {
let u0v = frame0[(i0, l0)];
for l1 in 0..k1 {
let u01v = u0v * frame1[(i1, l1)];
for l2 in 0..k2 {
acc[l0 * k1 * k2 + l1 * k2 + l2] +=
u01v * frame2[(i2, l2)] * val;
}
}
}
}
}
acc
},
)
.reduce(
|| vec![0.0; kk],
|mut a, b| {
for i in 0..kk {
a[i] += b[i];
}
a
},
);
let mut root_mat: Mat<f64> = Mat::zeros(k0, k3);
for l0 in 0..k0 {
for t3 in 0..k3 {
let mut sum = 0.0;
for l1 in 0..k1 {
for l2 in 0..k2 {
sum += transfer3[t3 * k1 * k2 + l1 * k2 + l2]
* m_tensor[l0 * k1 * k2 + l1 * k2 + l2];
}
}
root_mat[(l0, t3)] = sum;
}
}
let k_root = 1;
let mut transfer_root = vec![0.0; k0 * k3];
for l0 in 0..k0 {
for t3 in 0..k3 {
transfer_root[l0 * k3 + t3] = root_mat[(l0, t3)];
}
}
let mut nodes = Vec::with_capacity(NUM_NODES_3D);
nodes.push(HtNode3D::Leaf {
dim: 0,
frame: frame0,
});
nodes.push(HtNode3D::Leaf {
dim: 1,
frame: frame1,
});
nodes.push(HtNode3D::Leaf {
dim: 2,
frame: frame2,
});
nodes.push(HtNode3D::Interior {
left: 1,
right: 2,
transfer: transfer3,
ranks: [k3, k1, k2],
});
nodes.push(HtNode3D::Interior {
left: 0,
right: 3,
transfer: transfer_root,
ranks: [k_root, k0, k3],
});
Self { nodes, shape, dx }
}
pub fn zero_pad(&self) -> HtTensor3D {
let new_shape = [self.shape[0] * 2, self.shape[1] * 2, self.shape[2] * 2];
let mut nodes = Vec::with_capacity(NUM_NODES_3D);
for (i, node) in self.nodes.iter().enumerate() {
match node {
HtNode3D::Leaf { dim, frame } => {
let n = frame.nrows();
let k = frame.ncols();
let new_n = n * 2;
let mut new_frame = Mat::zeros(new_n, k);
for r in 0..n {
for c in 0..k {
new_frame[(r, c)] = frame[(r, c)];
}
}
nodes.push(HtNode3D::Leaf {
dim: *dim,
frame: new_frame,
});
}
HtNode3D::Interior {
left,
right,
transfer,
ranks,
} => {
nodes.push(HtNode3D::Interior {
left: *left,
right: *right,
transfer: transfer.clone(),
ranks: *ranks,
});
}
}
}
HtTensor3D {
nodes,
shape: new_shape,
dx: self.dx,
}
}
pub fn into_zero_padded(self) -> HtTensor3D {
let new_shape = [self.shape[0] * 2, self.shape[1] * 2, self.shape[2] * 2];
let mut nodes = Vec::with_capacity(NUM_NODES_3D);
for node in self.nodes {
match node {
HtNode3D::Leaf { dim, frame } => {
let n = frame.nrows();
let k = frame.ncols();
let new_n = n * 2;
let mut new_frame = Mat::zeros(new_n, k);
for r in 0..n {
for c in 0..k {
new_frame[(r, c)] = frame[(r, c)];
}
}
nodes.push(HtNode3D::Leaf {
dim,
frame: new_frame,
});
}
HtNode3D::Interior {
left,
right,
transfer,
ranks,
} => {
nodes.push(HtNode3D::Interior {
left,
right,
transfer,
ranks,
});
}
}
}
HtTensor3D {
nodes,
shape: new_shape,
dx: self.dx,
}
}
pub fn extract_subgrid(&self, shape: [usize; 3]) -> HtTensor3D {
let mut nodes = Vec::with_capacity(NUM_NODES_3D);
for node in &self.nodes {
match node {
HtNode3D::Leaf { dim, frame } => {
let target_n = shape[*dim];
let k = frame.ncols();
let n = target_n.min(frame.nrows());
let mut new_frame = Mat::zeros(n, k);
for r in 0..n {
for c in 0..k {
new_frame[(r, c)] = frame[(r, c)];
}
}
nodes.push(HtNode3D::Leaf {
dim: *dim,
frame: new_frame,
});
}
HtNode3D::Interior {
left,
right,
transfer,
ranks,
} => {
nodes.push(HtNode3D::Interior {
left: *left,
right: *right,
transfer: transfer.clone(),
ranks: *ranks,
});
}
}
}
HtTensor3D {
nodes,
shape,
dx: self.dx,
}
}
pub fn into_subgrid(self, shape: [usize; 3]) -> HtTensor3D {
let mut nodes = Vec::with_capacity(NUM_NODES_3D);
for node in self.nodes {
match node {
HtNode3D::Leaf { dim, frame } => {
let target_n = shape[dim];
let k = frame.ncols();
let n = target_n.min(frame.nrows());
let mut new_frame = Mat::zeros(n, k);
for r in 0..n {
for c in 0..k {
new_frame[(r, c)] = frame[(r, c)];
}
}
nodes.push(HtNode3D::Leaf {
dim,
frame: new_frame,
});
}
HtNode3D::Interior {
left,
right,
transfer,
ranks,
} => {
nodes.push(HtNode3D::Interior {
left,
right,
transfer,
ranks,
});
}
}
}
HtTensor3D {
nodes,
shape,
dx: self.dx,
}
}
pub fn to_full_3d(&self) -> Vec<f64> {
self.to_dense_subgrid(self.shape)
}
pub fn to_dense_subgrid(&self, sub: [usize; 3]) -> Vec<f64> {
let [s0, s1, s2] = sub;
let (u0, u1, u2) = self.leaf_frames();
let (b3, rk3) = self.interior_transfer(3);
let (b4, rk4) = self.interior_transfer(4);
let [_kt3, k1, k2] = rk3;
let [_kt4, k0, k3] = rk4;
let mut t1 = vec![0.0f64; k3 * k1 * s2];
for j3 in 0..k3 {
for j1 in 0..k1 {
for i2 in 0..s2 {
let mut sum = 0.0;
for j2 in 0..k2 {
sum += b3[j3 * k1 * k2 + j1 * k2 + j2] * u2[(i2, j2)];
}
t1[j3 * k1 * s2 + j1 * s2 + i2] = sum;
}
}
}
let mut t2 = vec![0.0f64; k3 * s1 * s2];
for j3 in 0..k3 {
for i1 in 0..s1 {
for i2 in 0..s2 {
let mut sum = 0.0;
for j1 in 0..k1 {
sum += u1[(i1, j1)] * t1[j3 * k1 * s2 + j1 * s2 + i2];
}
t2[j3 * s1 * s2 + i1 * s2 + i2] = sum;
}
}
}
let mut t3 = vec![0.0f64; k0 * s1 * s2];
for j0 in 0..k0 {
for i1 in 0..s1 {
for i2 in 0..s2 {
let mut sum = 0.0;
for j3 in 0..k3 {
sum += b4[j0 * k3 + j3] * t2[j3 * s1 * s2 + i1 * s2 + i2];
}
t3[j0 * s1 * s2 + i1 * s2 + i2] = sum;
}
}
}
let slab_size = s1 * s2;
let mut data = vec![0.0f64; s0 * slab_size];
data.par_chunks_mut(slab_size)
.enumerate()
.for_each(|(i0, chunk)| {
for i1 in 0..s1 {
for i2 in 0..s2 {
let mut sum = 0.0;
for j0 in 0..k0 {
sum += u0[(i0, j0)] * t3[j0 * slab_size + i1 * s2 + i2];
}
chunk[i1 * s2 + i2] = sum;
}
}
});
data
}
fn leaf_frames(&self) -> (&Mat<f64>, &Mat<f64>, &Mat<f64>) {
let f0 = match &self.nodes[0] {
HtNode3D::Leaf { frame, .. } => frame,
_ => {
debug_assert!(false, "Node 0 is not a leaf");
match &self.nodes[0] {
HtNode3D::Leaf { frame, .. } => frame,
HtNode3D::Interior { .. } => {
static EMPTY: std::sync::LazyLock<Mat<f64>> =
std::sync::LazyLock::new(|| Mat::zeros(0, 0));
&EMPTY
}
}
}
};
let f1 = match &self.nodes[1] {
HtNode3D::Leaf { frame, .. } => frame,
_ => {
debug_assert!(false, "Node 1 is not a leaf");
static EMPTY: std::sync::LazyLock<Mat<f64>> =
std::sync::LazyLock::new(|| Mat::zeros(0, 0));
&EMPTY
}
};
let f2 = match &self.nodes[2] {
HtNode3D::Leaf { frame, .. } => frame,
_ => {
debug_assert!(false, "Node 2 is not a leaf");
static EMPTY: std::sync::LazyLock<Mat<f64>> =
std::sync::LazyLock::new(|| Mat::zeros(0, 0));
&EMPTY
}
};
(f0, f1, f2)
}
fn interior_transfer(&self, idx: usize) -> (&[f64], [usize; 3]) {
match &self.nodes[idx] {
HtNode3D::Interior {
transfer, ranks, ..
} => (transfer, *ranks),
_ => {
debug_assert!(false, "Node {idx} is not interior");
(&[], [0, 0, 0])
}
}
}
pub fn add(&self, other: &HtTensor3D) -> HtTensor3D {
assert_eq!(self.shape, other.shape, "Shape mismatch in HT3D addition");
let mut nodes = Vec::with_capacity(NUM_NODES_3D);
for leaf_idx in 0..NUM_LEAVES_3D {
let (frame_a, dim_a) = match &self.nodes[leaf_idx] {
HtNode3D::Leaf { dim, frame } => (frame, *dim),
_ => {
debug_assert!(false, "Expected leaf at index {leaf_idx}");
continue;
}
};
let frame_b = match &other.nodes[leaf_idx] {
HtNode3D::Leaf { frame, .. } => frame,
_ => {
debug_assert!(false, "Expected leaf at index {leaf_idx}");
continue;
}
};
let n = frame_a.nrows();
let ka = frame_a.ncols();
let kb = frame_b.ncols();
let mut new_frame = Mat::zeros(n, ka + kb);
for r in 0..n {
for c in 0..ka {
new_frame[(r, c)] = frame_a[(r, c)];
}
for c in 0..kb {
new_frame[(r, ka + c)] = frame_b[(r, c)];
}
}
nodes.push(HtNode3D::Leaf {
dim: dim_a,
frame: new_frame,
});
}
let (ta, ra) = self.interior_transfer(3);
let (tb, rb) = other.interior_transfer(3);
let new_kt3 = ra[0] + rb[0];
let new_kl3 = ra[1] + rb[1]; let new_kr3 = ra[2] + rb[2]; let mut new_t3 = vec![0.0; new_kt3 * new_kl3 * new_kr3];
for t in 0..ra[0] {
for l in 0..ra[1] {
for r in 0..ra[2] {
new_t3[t * new_kl3 * new_kr3 + l * new_kr3 + r] =
ta[t * ra[1] * ra[2] + l * ra[2] + r];
}
}
}
for t in 0..rb[0] {
for l in 0..rb[1] {
for r in 0..rb[2] {
new_t3[(ra[0] + t) * new_kl3 * new_kr3 + (ra[1] + l) * new_kr3 + (ra[2] + r)] =
tb[t * rb[1] * rb[2] + l * rb[2] + r];
}
}
}
nodes.push(HtNode3D::Interior {
left: 1,
right: 2,
transfer: new_t3,
ranks: [new_kt3, new_kl3, new_kr3],
});
let (ta_root, ra_root) = self.interior_transfer(4);
let (tb_root, rb_root) = other.interior_transfer(4);
let new_kt_root = 1; let new_kl_root = ra_root[1] + rb_root[1]; let new_kr_root = ra_root[2] + rb_root[2]; let mut new_t_root = vec![0.0; new_kl_root * new_kr_root];
for l in 0..ra_root[1] {
for r in 0..ra_root[2] {
new_t_root[l * new_kr_root + r] = ta_root[l * ra_root[2] + r]; }
}
for l in 0..rb_root[1] {
for r in 0..rb_root[2] {
new_t_root[(ra_root[1] + l) * new_kr_root + (ra_root[2] + r)] =
tb_root[l * rb_root[2] + r];
}
}
nodes.push(HtNode3D::Interior {
left: 0,
right: 3,
transfer: new_t_root,
ranks: [new_kt_root, new_kl_root, new_kr_root],
});
HtTensor3D {
nodes,
shape: self.shape,
dx: self.dx,
}
}
pub fn truncate(&mut self, eps: f64, max_rank: usize) {
let (q0, r0) = leaf_qr(&self.nodes[0]);
self.nodes[0] = HtNode3D::Leaf { dim: 0, frame: q0 };
let (q1, r1) = leaf_qr(&self.nodes[1]);
self.nodes[1] = HtNode3D::Leaf { dim: 1, frame: q1 };
let (q2, r2) = leaf_qr(&self.nodes[2]);
self.nodes[2] = HtNode3D::Leaf { dim: 2, frame: q2 };
let (b3, rk3) = match &self.nodes[3] {
HtNode3D::Interior {
transfer, ranks, ..
} => (transfer.clone(), *ranks),
_ => {
debug_assert!(false, "Node 3 not interior");
return;
}
};
let [kt3, k1, k2] = rk3;
let k1_new = r1.nrows(); let k2_new = r2.nrows();
let mut b3_abs = vec![0.0f64; kt3 * k1_new * k2_new];
for t in 0..kt3 {
for lp in 0..k1_new {
for rp in 0..k2_new {
let mut sum = 0.0;
for l in 0..k1 {
let r1_lp_l = r1[(lp, l)];
if r1_lp_l.abs() < 1e-30 {
continue;
}
for r in 0..k2 {
sum += r1_lp_l * b3[t * k1 * k2 + l * k2 + r] * r2[(rp, r)];
}
}
b3_abs[t * k1_new * k2_new + lp * k2_new + rp] = sum;
}
}
}
let mat3_rows = k1_new * k2_new;
let mut mat3 = Mat::<f64>::zeros(mat3_rows, kt3);
for t in 0..kt3 {
for l in 0..k1_new {
for r in 0..k2_new {
mat3[(l * k2_new + r, t)] = b3_abs[t * k1_new * k2_new + l * k2_new + r];
}
}
}
let qr3 = mat3.as_ref().qr();
let q3 = qr3.compute_thin_Q(); let r3 = qr3.thin_R().to_owned(); let kt3_orth = q3.ncols();
let mut b3_orth = vec![0.0f64; kt3_orth * k1_new * k2_new];
for tp in 0..kt3_orth {
for l in 0..k1_new {
for r in 0..k2_new {
b3_orth[tp * k1_new * k2_new + l * k2_new + r] = q3[(l * k2_new + r, tp)];
}
}
}
let (b4, rk4) = match &self.nodes[4] {
HtNode3D::Interior {
transfer, ranks, ..
} => (transfer.clone(), *ranks),
_ => {
debug_assert!(false, "Node 4 not interior");
return;
}
};
let [_kt4, k0, k3_old] = rk4;
let k0_new = r0.nrows(); let mut b4_abs = Mat::<f64>::zeros(k0_new, kt3_orth);
for jp in 0..k0_new {
for tp in 0..kt3_orth {
let mut sum = 0.0;
for j0 in 0..k0 {
let r0_jp_j0 = r0[(jp, j0)];
if r0_jp_j0.abs() < 1e-30 {
continue;
}
for j3 in 0..k3_old {
sum += r0_jp_j0 * b4[j0 * k3_old + j3] * r3[(tp, j3)];
}
}
b4_abs[(jp, tp)] = sum;
}
}
let svd_result = b4_abs.as_ref().thin_svd();
let (new_rank, new_leaf0, new_b3, new_b4) = match svd_result {
Ok(svd) => {
let s_col = svd.S().column_vector();
let sv: Vec<f64> = (0..s_col.nrows()).map(|i| s_col[i]).collect();
let u_full = svd.U();
let v_full = svd.V();
let trunc_k = truncation_rank(&sv, eps)
.min(max_rank)
.max(1)
.min(u_full.ncols());
let leaf0_frame = match &self.nodes[0] {
HtNode3D::Leaf { frame, .. } => frame,
_ => unreachable!(),
};
let mut new_leaf0 = Mat::<f64>::zeros(leaf0_frame.nrows(), trunc_k);
for i in 0..leaf0_frame.nrows() {
for j in 0..trunc_k {
let mut val = 0.0;
for jp in 0..k0_new {
val += leaf0_frame[(i, jp)] * u_full[(jp, j)];
}
new_leaf0[(i, j)] = val * sv[j].sqrt();
}
}
let mut new_b3 = vec![0.0f64; trunc_k * k1_new * k2_new];
for j in 0..trunc_k {
let sqrt_s = sv[j].sqrt();
for l in 0..k1_new {
for r in 0..k2_new {
let mut sum = 0.0;
for tp in 0..kt3_orth {
sum += v_full[(tp, j)]
* b3_orth[tp * k1_new * k2_new + l * k2_new + r];
}
new_b3[j * k1_new * k2_new + l * k2_new + r] = sqrt_s * sum;
}
}
}
let mut new_b4 = vec![0.0f64; trunc_k * trunc_k];
for j in 0..trunc_k {
new_b4[j * trunc_k + j] = 1.0;
}
(trunc_k, new_leaf0, new_b3, new_b4)
}
Err(_) => {
let leaf0_frame = match &self.nodes[0] {
HtNode3D::Leaf { frame, .. } => frame,
_ => unreachable!(),
};
let n0 = leaf0_frame.nrows();
let mut new_leaf0 = Mat::<f64>::zeros(n0, 1);
new_leaf0[(0, 0)] = 1.0;
let new_b3 = vec![0.0f64; k1_new * k2_new];
let new_b4 = vec![1.0f64; 1];
(1, new_leaf0, new_b3, new_b4)
}
};
self.nodes[0] = HtNode3D::Leaf {
dim: 0,
frame: new_leaf0,
};
self.nodes[3] = HtNode3D::Interior {
left: 1,
right: 2,
transfer: new_b3,
ranks: [new_rank, k1_new, k2_new],
};
self.nodes[4] = HtNode3D::Interior {
left: 0,
right: 3,
transfer: new_b4,
ranks: [1, new_rank, new_rank],
};
}
pub fn zero(shape: [usize; 3], dx: [f64; 3]) -> Self {
let mut nodes = Vec::with_capacity(NUM_NODES_3D);
for d in 0..NUM_LEAVES_3D {
let n = shape[d];
let mut frame = Mat::zeros(n, 1);
frame[(0, 0)] = 1.0; nodes.push(HtNode3D::Leaf { dim: d, frame });
}
nodes.push(HtNode3D::Interior {
left: 1,
right: 2,
transfer: vec![0.0; 1],
ranks: [1, 1, 1],
});
nodes.push(HtNode3D::Interior {
left: 0,
right: 3,
transfer: vec![0.0; 1],
ranks: [1, 1, 1],
});
Self { nodes, shape, dx }
}
pub fn ranks(&self) -> Vec<usize> {
self.nodes.iter().map(|n| n.rank()).collect()
}
pub fn norm_sq(&self) -> f64 {
let data = self.to_full_3d();
data.iter().map(|x| x * x).sum()
}
}
#[derive(Clone)]
pub enum HtNode3DComplex {
Leaf {
dim: usize,
frame_re: Mat<f64>,
frame_im: Mat<f64>,
},
Interior {
left: usize,
right: usize,
transfer: Vec<f64>,
ranks: [usize; 3],
},
}
impl HtNode3DComplex {
#[inline]
pub fn rank(&self) -> usize {
match self {
HtNode3DComplex::Leaf { frame_re, .. } => frame_re.ncols(),
HtNode3DComplex::Interior { ranks, .. } => ranks[0],
}
}
}
#[derive(Clone)]
pub struct HtTensor3DComplex {
pub nodes: Vec<HtNode3DComplex>,
pub shape: [usize; 3],
pub dx: [f64; 3],
}
impl HtTensor3DComplex {
pub fn evaluate(&self, idx: [usize; 3]) -> (f64, f64) {
let (u0_re, u0_im) = self.leaf_vector_complex(0, idx[0]);
let (u1_re, u1_im) = self.leaf_vector_complex(1, idx[1]);
let (u2_re, u2_im) = self.leaf_vector_complex(2, idx[2]);
let (z3_re, z3_im) = self.contract_interior_complex(3, &u1_re, &u1_im, &u2_re, &u2_im);
let (z4_re, z4_im) = self.contract_interior_complex(4, &u0_re, &u0_im, &z3_re, &z3_im);
(z4_re[0], z4_im[0])
}
#[inline]
fn leaf_vector_complex(&self, node: usize, idx: usize) -> (Vec<f64>, Vec<f64>) {
match &self.nodes[node] {
HtNode3DComplex::Leaf {
frame_re, frame_im, ..
} => {
let k = frame_re.ncols();
let re: Vec<f64> = (0..k).map(|j| frame_re[(idx, j)]).collect();
let im: Vec<f64> = (0..k).map(|j| frame_im[(idx, j)]).collect();
(re, im)
}
_ => {
debug_assert!(false, "Node {node} is not a leaf");
(vec![], vec![])
}
}
}
#[inline]
fn contract_interior_complex(
&self,
node: usize,
left_re: &[f64],
left_im: &[f64],
right_re: &[f64],
right_im: &[f64],
) -> (Vec<f64>, Vec<f64>) {
match &self.nodes[node] {
HtNode3DComplex::Interior {
transfer, ranks, ..
} => {
let [kt, kl, kr] = *ranks;
let mut re = vec![0.0; kt];
let mut im = vec![0.0; kt];
for t in 0..kt {
let mut sum_re = 0.0;
let mut sum_im = 0.0;
for l in 0..kl {
for r in 0..kr {
let b = transfer[t * kl * kr + l * kr + r];
let lr_re = left_re[l] * right_re[r] - left_im[l] * right_im[r];
let lr_im = left_re[l] * right_im[r] + left_im[l] * right_re[r];
sum_re += b * lr_re;
sum_im += b * lr_im;
}
}
re[t] = sum_re;
im[t] = sum_im;
}
(re, im)
}
_ => {
debug_assert!(false, "Node {node} is not an interior node");
(vec![], vec![])
}
}
}
pub fn ranks(&self) -> Vec<usize> {
self.nodes.iter().map(|n| n.rank()).collect()
}
}
impl HtTensor3D {
pub fn fft_leaves(&self) -> HtTensor3DComplex {
use rustfft::FftPlanner;
use rustfft::num_complex::Complex64;
let mut nodes = Vec::with_capacity(NUM_NODES_3D);
for node in &self.nodes {
match node {
HtNode3D::Leaf { dim, frame } => {
let n = frame.nrows();
let k = frame.ncols();
let mut frame_re = Mat::zeros(n, k);
let mut frame_im = Mat::zeros(n, k);
let mut planner = FftPlanner::new();
let fft = planner.plan_fft_forward(n);
for col in 0..k {
let mut buffer: Vec<Complex64> = (0..n)
.map(|r| Complex64::new(frame[(r, col)], 0.0))
.collect();
fft.process(&mut buffer);
for r in 0..n {
frame_re[(r, col)] = buffer[r].re;
frame_im[(r, col)] = buffer[r].im;
}
}
nodes.push(HtNode3DComplex::Leaf {
dim: *dim,
frame_re,
frame_im,
});
}
HtNode3D::Interior {
left,
right,
transfer,
ranks,
} => {
nodes.push(HtNode3DComplex::Interior {
left: *left,
right: *right,
transfer: transfer.clone(),
ranks: *ranks,
});
}
}
}
HtTensor3DComplex {
nodes,
shape: self.shape,
dx: self.dx,
}
}
pub fn fft_leaves_with_plans(
&self,
plans: &[std::sync::Arc<dyn rustfft::Fft<f64>>; 3],
) -> HtTensor3DComplex {
use rustfft::num_complex::Complex64;
let mut nodes = Vec::with_capacity(NUM_NODES_3D);
for node in &self.nodes {
match node {
HtNode3D::Leaf { dim, frame } => {
let n = frame.nrows();
let k = frame.ncols();
let mut frame_re = Mat::zeros(n, k);
let mut frame_im = Mat::zeros(n, k);
let fft = &plans[*dim];
for col in 0..k {
let mut buffer: Vec<Complex64> = (0..n)
.map(|r| Complex64::new(frame[(r, col)], 0.0))
.collect();
fft.process(&mut buffer);
for r in 0..n {
frame_re[(r, col)] = buffer[r].re;
frame_im[(r, col)] = buffer[r].im;
}
}
nodes.push(HtNode3DComplex::Leaf {
dim: *dim,
frame_re,
frame_im,
});
}
HtNode3D::Interior {
left,
right,
transfer,
ranks,
} => {
nodes.push(HtNode3DComplex::Interior {
left: *left,
right: *right,
transfer: transfer.clone(),
ranks: *ranks,
});
}
}
}
HtTensor3DComplex {
nodes,
shape: self.shape,
dx: self.dx,
}
}
pub fn into_fft_with_plans(
self,
plans: &[std::sync::Arc<dyn rustfft::Fft<f64>>; 3],
) -> HtTensor3DComplex {
use rustfft::num_complex::Complex64;
let mut nodes = Vec::with_capacity(NUM_NODES_3D);
for node in self.nodes {
match node {
HtNode3D::Leaf { dim, frame } => {
let n = frame.nrows();
let k = frame.ncols();
let mut frame_re = Mat::zeros(n, k);
let mut frame_im = Mat::zeros(n, k);
let fft = &plans[dim];
for col in 0..k {
let mut buffer: Vec<Complex64> = (0..n)
.map(|r| Complex64::new(frame[(r, col)], 0.0))
.collect();
fft.process(&mut buffer);
for r in 0..n {
frame_re[(r, col)] = buffer[r].re;
frame_im[(r, col)] = buffer[r].im;
}
}
nodes.push(HtNode3DComplex::Leaf {
dim,
frame_re,
frame_im,
});
}
HtNode3D::Interior {
left,
right,
transfer,
ranks,
} => {
nodes.push(HtNode3DComplex::Interior {
left,
right,
transfer,
ranks,
});
}
}
}
HtTensor3DComplex {
nodes,
shape: self.shape,
dx: self.dx,
}
}
pub fn from_rank1(v0: &[f64], v1: &[f64], v2: &[f64], dx: [f64; 3]) -> Self {
let shape = [v0.len(), v1.len(), v2.len()];
let mut nodes = Vec::with_capacity(NUM_NODES_3D);
for (d, vec) in [v0, v1, v2].iter().enumerate() {
let n = vec.len();
let mut frame = Mat::zeros(n, 1);
for i in 0..n {
frame[(i, 0)] = vec[i];
}
nodes.push(HtNode3D::Leaf { dim: d, frame });
}
nodes.push(HtNode3D::Interior {
left: 1,
right: 2,
transfer: vec![1.0],
ranks: [1, 1, 1],
});
nodes.push(HtNode3D::Interior {
left: 0,
right: 3,
transfer: vec![1.0],
ranks: [1, 1, 1],
});
Self { nodes, shape, dx }
}
}
impl HtTensor3DComplex {
pub fn ifft_leaves(&self) -> HtTensor3D {
use rustfft::FftPlanner;
use rustfft::num_complex::Complex64;
let mut nodes = Vec::with_capacity(NUM_NODES_3D);
for node in &self.nodes {
match node {
HtNode3DComplex::Leaf {
dim,
frame_re,
frame_im,
} => {
let n = frame_re.nrows();
let k = frame_re.ncols();
let mut frame = Mat::zeros(n, k);
let mut planner = FftPlanner::new();
let ifft = planner.plan_fft_inverse(n);
let scale = 1.0 / n as f64;
for col in 0..k {
let mut buffer: Vec<Complex64> = (0..n)
.map(|r| Complex64::new(frame_re[(r, col)], frame_im[(r, col)]))
.collect();
ifft.process(&mut buffer);
for r in 0..n {
frame[(r, col)] = buffer[r].re * scale;
}
}
nodes.push(HtNode3D::Leaf { dim: *dim, frame });
}
HtNode3DComplex::Interior {
left,
right,
transfer,
ranks,
} => {
nodes.push(HtNode3D::Interior {
left: *left,
right: *right,
transfer: transfer.clone(),
ranks: *ranks,
});
}
}
}
HtTensor3D {
nodes,
shape: self.shape,
dx: self.dx,
}
}
pub fn ifft_leaves_with_plans(
&self,
plans: &[std::sync::Arc<dyn rustfft::Fft<f64>>; 3],
) -> HtTensor3D {
use rustfft::num_complex::Complex64;
let mut nodes = Vec::with_capacity(NUM_NODES_3D);
for node in &self.nodes {
match node {
HtNode3DComplex::Leaf {
dim,
frame_re,
frame_im,
} => {
let n = frame_re.nrows();
let k = frame_re.ncols();
let mut frame = Mat::zeros(n, k);
let ifft = &plans[*dim];
let scale = 1.0 / n as f64;
for col in 0..k {
let mut buffer: Vec<Complex64> = (0..n)
.map(|r| Complex64::new(frame_re[(r, col)], frame_im[(r, col)]))
.collect();
ifft.process(&mut buffer);
for r in 0..n {
frame[(r, col)] = buffer[r].re * scale;
}
}
nodes.push(HtNode3D::Leaf { dim: *dim, frame });
}
HtNode3DComplex::Interior {
left,
right,
transfer,
ranks,
} => {
nodes.push(HtNode3D::Interior {
left: *left,
right: *right,
transfer: transfer.clone(),
ranks: *ranks,
});
}
}
}
HtTensor3D {
nodes,
shape: self.shape,
dx: self.dx,
}
}
pub fn into_ifft_with_plans(
self,
plans: &[std::sync::Arc<dyn rustfft::Fft<f64>>; 3],
) -> HtTensor3D {
use rustfft::num_complex::Complex64;
let mut nodes = Vec::with_capacity(NUM_NODES_3D);
for node in self.nodes {
match node {
HtNode3DComplex::Leaf {
dim,
frame_re,
frame_im,
} => {
let n = frame_re.nrows();
let k = frame_re.ncols();
let mut frame = Mat::zeros(n, k);
let ifft = &plans[dim];
let scale = 1.0 / n as f64;
for col in 0..k {
let mut buffer: Vec<Complex64> = (0..n)
.map(|r| Complex64::new(frame_re[(r, col)], frame_im[(r, col)]))
.collect();
ifft.process(&mut buffer);
for r in 0..n {
frame[(r, col)] = buffer[r].re * scale;
}
}
nodes.push(HtNode3D::Leaf { dim, frame });
}
HtNode3DComplex::Interior {
left,
right,
transfer,
ranks,
} => {
nodes.push(HtNode3D::Interior {
left,
right,
transfer,
ranks,
});
}
}
}
HtTensor3D {
nodes,
shape: self.shape,
dx: self.dx,
}
}
}
fn leaf_qr(node: &HtNode3D) -> (Mat<f64>, Mat<f64>) {
match node {
HtNode3D::Leaf { frame, .. } => {
let m = frame.nrows();
let n = frame.ncols();
if m == 0 || n == 0 {
return (Mat::zeros(m.max(1), 1), Mat::zeros(1, n.max(1)));
}
let qr = frame.as_ref().qr();
(qr.compute_thin_Q(), qr.thin_R().to_owned())
}
_ => {
debug_assert!(false, "leaf_qr called on non-leaf node");
(Mat::zeros(1, 1), Mat::zeros(1, 1))
}
}
}
fn mode_unfolding_svd(
data: &[f64],
rows: usize,
cols: usize,
eps: f64,
max_rank: usize,
) -> (usize, Mat<f64>) {
let mut mat: Mat<f64> = Mat::zeros(rows, cols);
for r in 0..rows {
for c in 0..cols {
mat[(r, c)] = data[r * cols + c];
}
}
let svd = mat.as_ref().thin_svd();
match svd {
Ok(svd) => {
let s_col = svd.S().column_vector();
let sv: Vec<f64> = (0..s_col.nrows()).map(|i| s_col[i]).collect();
let rank = truncation_rank(&sv, eps)
.min(max_rank)
.min(svd.U().ncols())
.max(1);
let u = svd.U();
let mut frame = Mat::zeros(rows, rank);
for r in 0..rows {
for c in 0..rank {
frame[(r, c)] = u[(r, c)];
}
}
(rank, frame)
}
Err(_) => {
let mut frame = Mat::zeros(rows, 1);
frame[(0, 0)] = 1.0;
(1, frame)
}
}
}
fn truncation_rank(sv: &[f64], eps: f64) -> usize {
let eps2 = eps * eps;
let mut tail_sq = 0.0;
for k in (0..sv.len()).rev() {
tail_sq += sv[k] * sv[k];
if tail_sq > eps2 {
return k + 1;
}
}
1
}
fn truncation_rank_mat(mat: &Mat<f64>, eps: f64) -> usize {
if let Ok(svd) = mat.as_ref().thin_svd() {
let s_col = svd.S().column_vector();
let sv: Vec<f64> = (0..s_col.nrows()).map(|i| s_col[i]).collect();
truncation_rank(&sv, eps)
} else {
1
}
}
fn extract_frame_qr(mat: &Mat<f64>, max_rank: usize) -> Mat<f64> {
let m = mat.nrows();
let n = mat.ncols();
if m == 0 || n == 0 {
return Mat::zeros(m.max(1), 1);
}
let qr = mat.as_ref().col_piv_qr();
let q = qr.compute_thin_Q();
let r = qr.thin_R();
let k = m.min(n);
let mut rank = 0;
let threshold = 1e-12 * r[(0, 0)].abs();
for i in 0..k {
if i < r.nrows() && i < r.ncols() && r[(i, i)].abs() > threshold {
rank = i + 1;
}
}
rank = rank.max(1).min(max_rank);
let mut frame = Mat::zeros(m, rank);
for r in 0..m {
for c in 0..rank {
if c < q.ncols() {
frame[(r, c)] = q[(r, c)];
}
}
}
frame
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ht3d_rank1_roundtrip() {
let v0: Vec<f64> = (0..8).map(|i| i as f64 + 1.0).collect();
let v1: Vec<f64> = (0..8).map(|i| (i as f64 + 1.0) * 0.5).collect();
let v2: Vec<f64> = (0..8).map(|i| (i as f64 + 1.0) * 0.3).collect();
let dx = [0.25; 3];
let ht = HtTensor3D::from_rank1(&v0, &v1, &v2, dx);
let mut max_err = 0.0f64;
for i0 in 0..8 {
for i1 in 0..8 {
for i2 in 0..8 {
let expected = v0[i0] * v1[i1] * v2[i2];
let got = ht.evaluate([i0, i1, i2]);
max_err = max_err.max((got - expected).abs());
}
}
}
assert!(max_err < 1e-12, "Rank-1 round-trip error: {max_err}");
}
#[test]
fn ht3d_dense_roundtrip() {
let shape = [4, 4, 4];
let n = 64;
let mut data = vec![0.0; n];
for i0 in 0..4 {
for i1 in 0..4 {
for i2 in 0..4 {
data[i0 * 16 + i1 * 4 + i2] =
(i0 as f64 + 1.0) * (i1 as f64 + 0.5) + (i2 as f64) * 0.1;
}
}
}
let dx = [0.5; 3];
let ht = HtTensor3D::from_dense(&data, shape, dx, 1e-10, 10);
let reconstructed = ht.to_full_3d();
let max_err: f64 = data
.iter()
.zip(reconstructed.iter())
.map(|(a, b)| (a - b).abs())
.fold(0.0, f64::max);
assert!(
max_err < 1e-8,
"Dense round-trip error: {max_err}, ranks: {:?}",
ht.ranks()
);
}
#[test]
fn ht3d_zero_pad_and_extract() {
let v0: Vec<f64> = (0..4).map(|i| i as f64 + 1.0).collect();
let v1: Vec<f64> = (0..4).map(|i| (i as f64 + 1.0) * 0.5).collect();
let v2: Vec<f64> = (0..4).map(|i| (i as f64 + 1.0) * 0.3).collect();
let dx = [0.5; 3];
let ht = HtTensor3D::from_rank1(&v0, &v1, &v2, dx);
let padded = ht.zero_pad();
assert_eq!(padded.shape, [8, 8, 8]);
for i0 in 0..4 {
for i1 in 0..4 {
for i2 in 0..4 {
let expected = v0[i0] * v1[i1] * v2[i2];
let got = padded.evaluate([i0, i1, i2]);
assert!(
(got - expected).abs() < 1e-12,
"Padded value mismatch at [{i0},{i1},{i2}]: {got} vs {expected}"
);
}
}
}
for i2 in 4..8 {
let val = padded.evaluate([0, 0, i2]);
assert!(val.abs() < 1e-12, "Padded region should be zero, got {val}");
}
let extracted = padded.extract_subgrid([4, 4, 4]);
for i0 in 0..4 {
for i1 in 0..4 {
for i2 in 0..4 {
let expected = v0[i0] * v1[i1] * v2[i2];
let got = extracted.evaluate([i0, i1, i2]);
assert!(
(got - expected).abs() < 1e-12,
"Extracted value mismatch at [{i0},{i1},{i2}]"
);
}
}
}
}
#[test]
fn ht3d_addition() {
let dx = [0.5; 3];
let a = HtTensor3D::from_rank1(
&[1.0, 2.0, 3.0, 4.0],
&[1.0, 1.0, 1.0, 1.0],
&[0.5, 0.5, 0.5, 0.5],
dx,
);
let b = HtTensor3D::from_rank1(
&[0.0, 0.0, 0.0, 1.0],
&[1.0, 2.0, 3.0, 4.0],
&[1.0, 1.0, 1.0, 1.0],
dx,
);
let sum = a.add(&b);
for i0 in 0..4 {
for i1 in 0..4 {
for i2 in 0..4 {
let expected = a.evaluate([i0, i1, i2]) + b.evaluate([i0, i1, i2]);
let got = sum.evaluate([i0, i1, i2]);
assert!(
(got - expected).abs() < 1e-10,
"Addition mismatch at [{i0},{i1},{i2}]: {got} vs {expected}"
);
}
}
}
}
#[test]
fn ht3d_fft_roundtrip() {
let v0: Vec<f64> = (0..8).map(|i| (-(i as f64 - 3.5).powi(2)).exp()).collect();
let v1: Vec<f64> = (0..8).map(|i| (-(i as f64 - 3.5).powi(2)).exp()).collect();
let v2: Vec<f64> = (0..8).map(|i| (-(i as f64 - 3.5).powi(2)).exp()).collect();
let dx = [0.25; 3];
let ht = HtTensor3D::from_rank1(&v0, &v1, &v2, dx);
let original = ht.to_full_3d();
let complex = ht.fft_leaves();
let recovered = complex.ifft_leaves();
let recovered_data = recovered.to_full_3d();
let max_err: f64 = original
.iter()
.zip(recovered_data.iter())
.map(|(a, b)| (a - b).abs())
.fold(0.0, f64::max);
assert!(max_err < 1e-10, "FFT round-trip error: {max_err}");
}
#[test]
fn ht3d_fft_preserves_rank() {
let v0: Vec<f64> = (0..8).map(|i| i as f64 + 1.0).collect();
let v1: Vec<f64> = (0..8).map(|i| i as f64 * 0.5).collect();
let v2: Vec<f64> = (0..8).map(|i| i as f64 * 0.3 + 1.0).collect();
let dx = [0.25; 3];
let ht = HtTensor3D::from_rank1(&v0, &v1, &v2, dx);
let ranks_before = ht.ranks();
let complex = ht.fft_leaves();
let ranks_after = complex.ranks();
assert_eq!(
ranks_before, ranks_after,
"FFT should preserve ranks: before={ranks_before:?}, after={ranks_after:?}"
);
}
#[test]
fn ht3d_from_function_aca() {
let shape = [8, 8, 8];
let dx = [0.25; 3];
let f = |idx: [usize; 3]| -> f64 {
let x = idx[0] as f64 - 3.5;
let y = idx[1] as f64 - 3.5;
let z = idx[2] as f64 - 3.5;
(-(x * x + y * y + z * z) / 4.0).exp()
};
let ht = HtTensor3D::from_function_aca(&f, shape, dx, 1e-6, 10);
let mut max_err = 0.0f64;
for i0 in 0..8 {
for i1 in 0..8 {
for i2 in 0..8 {
let expected = f([i0, i1, i2]);
let got = ht.evaluate([i0, i1, i2]);
max_err = max_err.max((got - expected).abs());
}
}
}
assert!(
max_err < 1e-4,
"ACA construction error: {max_err}, ranks: {:?}",
ht.ranks()
);
}
#[test]
fn ht3d_truncate_inplace_vs_dense() {
let dx = [0.5; 3];
let shape = [8, 8, 8];
let a = HtTensor3D::from_rank1(
&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0],
&[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
&[0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5],
dx,
);
let b = HtTensor3D::from_rank1(
&[0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0],
&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0],
&[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
dx,
);
let sum = a.add(&b);
let reference = sum.to_full_3d();
let mut ht_inplace = sum.clone();
ht_inplace.truncate(1e-10, 10);
let reconstructed = ht_inplace.to_full_3d();
let max_err: f64 = reference
.iter()
.zip(reconstructed.iter())
.map(|(a, b)| (a - b).abs())
.fold(0.0, f64::max);
let norm: f64 = reference.iter().map(|x| x * x).sum::<f64>().sqrt();
assert!(
max_err / (norm + 1e-15) < 1e-8,
"In-place truncation error: {max_err}, norm: {norm}, ranks: {:?}",
ht_inplace.ranks()
);
}
#[test]
fn ht3d_to_dense_subgrid() {
let dx = [0.5; 3];
let shape = [8, 8, 8];
let a = HtTensor3D::from_rank1(
&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0],
&[0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0],
&[1.0, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3],
dx,
);
let b = HtTensor3D::from_rank1(
&[8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0],
&[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8],
&[0.3, 0.6, 0.9, 1.2, 1.5, 1.8, 2.1, 2.4],
dx,
);
let sum = a.add(&b);
let full = sum.to_full_3d();
let sub = sum.to_dense_subgrid([4, 4, 4]);
assert_eq!(sub.len(), 64);
let mut max_err = 0.0f64;
for i0 in 0..4 {
for i1 in 0..4 {
for i2 in 0..4 {
let expected = full[i0 * 64 + i1 * 8 + i2];
let got = sub[i0 * 16 + i1 * 4 + i2];
max_err = max_err.max((expected - got).abs());
}
}
}
assert!(max_err < 1e-10, "Subgrid extraction error: {max_err}");
}
}