use ariadnetor_core::backend::MemoryOrder;
use crate::block_sparse::{BlockCoord, BlockSparseLayout, BlockSparseTensorData, Direction};
use crate::sector::U1Sector;
use crate::test_fixtures::{out_in_legs, square_legs};
use crate::{Storage, TensorLayout};
fn sample_u1_rank2_data() -> BlockSparseTensorData<f64, U1Sector> {
BlockSparseTensorData::<f64, U1Sector>::zeros(
square_legs(vec![(U1Sector(0), 2), (U1Sector(1), 3)]),
U1Sector(0),
MemoryOrder::RowMajor,
)
}
#[test]
fn zeros_constructor_exposes_order_via_layout_only() {
let td = sample_u1_rank2_data();
assert_eq!(td.layout().order(), MemoryOrder::RowMajor);
let td_cm = BlockSparseTensorData::<f64, U1Sector>::zeros(
square_legs(vec![(U1Sector(0), 2), (U1Sector(1), 3)]),
U1Sector(0),
MemoryOrder::ColumnMajor,
);
assert_eq!(td_cm.layout().order(), MemoryOrder::ColumnMajor);
}
#[test]
fn block_data_accessor_joins_storage_and_layout() {
let mut td = sample_u1_rank2_data();
let d00 = td.block_data(&BlockCoord(vec![0, 0])).unwrap();
assert_eq!(d00.len(), 4);
assert!(d00.iter().all(|&v| v == 0.0));
let d11 = td.block_data(&BlockCoord(vec![1, 1])).unwrap();
assert_eq!(d11.len(), 9);
assert!(td.block_data(&BlockCoord(vec![0, 1])).is_none());
assert!(td.block_data(&BlockCoord(vec![1, 0])).is_none());
{
let slot = td.block_data_mut(&BlockCoord(vec![0, 0])).unwrap();
slot[0] = 7.0;
}
assert_eq!(td.block_data(&BlockCoord(vec![0, 0])).unwrap()[0], 7.0);
}
#[test]
fn logical_extent_and_storage_extent_diverge_when_symmetry_forbids_all_blocks() {
let layout: BlockSparseLayout<U1Sector> = BlockSparseLayout::new(
out_in_legs(vec![(U1Sector(0), 2)], vec![(U1Sector(0), 3)]),
U1Sector(1),
MemoryOrder::RowMajor,
);
let logical_extent: usize = TensorLayout::shape(&layout).iter().product();
assert_eq!(logical_extent, 6, "logical extent = product(shape) = 2*3");
assert_eq!(
TensorLayout::storage_extent(&layout),
0,
"storage_extent = sum of allowed-block sizes = 0 under forbidden flux",
);
assert!(logical_extent != TensorLayout::storage_extent(&layout));
}
#[test]
fn norm_lives_on_storage_half() {
let mut td = sample_u1_rank2_data();
assert_eq!(td.storage().norm(), 0.0);
{
let slot = td.block_data_mut(&BlockCoord(vec![0, 0])).unwrap();
slot[0] = 3.0;
slot[1] = 4.0;
}
let norm = td.storage().norm();
assert!(
(norm - 5.0).abs() < 1e-12,
"expected Frobenius norm 5.0, got {norm}",
);
let nf = td.storage().norm_frobenius();
assert!((nf - 5.0).abs() < 1e-12);
assert_eq!(td.storage().flat_len(), 13);
assert_eq!(td.storage().stored_len(), 13);
}
#[test]
fn dagger_flips_directions_duals_flux_and_conjugates_data() {
use num_complex::Complex64;
let mut td: BlockSparseTensorData<Complex64, U1Sector> = BlockSparseTensorData::zeros(
out_in_legs(
vec![(U1Sector(0), 2), (U1Sector(1), 2)],
vec![(U1Sector(0), 2)],
),
U1Sector(1),
MemoryOrder::RowMajor,
);
{
let slot = td.block_data_mut(&BlockCoord(vec![1, 0])).unwrap();
slot[0] = Complex64::new(1.0, 2.0);
slot[1] = Complex64::new(-3.0, 4.0);
}
let dag = td.dagger();
assert_eq!(*dag.layout().flux(), U1Sector(-1));
let dirs: Vec<Direction> = dag
.layout()
.indices()
.iter()
.map(|i| i.direction())
.collect();
assert_eq!(dirs, vec![Direction::In, Direction::Out]);
let d = dag.block_data(&BlockCoord(vec![1, 0])).unwrap();
assert_eq!(d[0], Complex64::new(1.0, -2.0));
assert_eq!(d[1], Complex64::new(-3.0, -4.0));
let back = dag.dagger();
assert_eq!(*back.layout().flux(), U1Sector(1));
let dirs_back: Vec<Direction> = back
.layout()
.indices()
.iter()
.map(|i| i.direction())
.collect();
assert_eq!(dirs_back, vec![Direction::Out, Direction::In]);
let db = back.block_data(&BlockCoord(vec![1, 0])).unwrap();
assert_eq!(db[0], Complex64::new(1.0, 2.0));
assert_eq!(db[1], Complex64::new(-3.0, 4.0));
}
#[test]
fn conj_preserves_layout_and_conjugates_data() {
use num_complex::Complex64;
let mut td: BlockSparseTensorData<Complex64, U1Sector> = BlockSparseTensorData::zeros(
square_legs(vec![(U1Sector(0), 2)]),
U1Sector(0),
MemoryOrder::RowMajor,
);
{
let slot = td.block_data_mut(&BlockCoord(vec![0, 0])).unwrap();
slot[0] = Complex64::new(1.0, 2.0);
}
let c = td.conj();
assert_eq!(*c.layout().flux(), U1Sector(0));
let dirs: Vec<Direction> = c.layout().indices().iter().map(|i| i.direction()).collect();
assert_eq!(dirs, vec![Direction::Out, Direction::In]);
let d = c.block_data(&BlockCoord(vec![0, 0])).unwrap();
assert_eq!(d[0], Complex64::new(1.0, -2.0));
}
#[test]
fn from_block_fn_populates_each_allowed_block_via_closure() {
let td: BlockSparseTensorData<f64, U1Sector> = BlockSparseTensorData::from_block_fn(
square_legs(vec![(U1Sector(0), 2), (U1Sector(1), 3)]),
U1Sector(0),
MemoryOrder::RowMajor,
|coord, block_shape| {
let tag = coord.0.iter().sum::<usize>() as f64;
let len: usize = block_shape.iter().product();
(0..len).map(|i| tag + (i as f64) * 0.1).collect()
},
);
let d00 = td.block_data(&BlockCoord(vec![0, 0])).unwrap();
assert_eq!(d00.len(), 4);
for (i, &v) in d00.iter().enumerate() {
let expected = (i as f64) * 0.1;
assert!(
(v - expected).abs() < 1e-12,
"block (0,0)[{i}] = {v}, expected ~{expected}",
);
}
let d11 = td.block_data(&BlockCoord(vec![1, 1])).unwrap();
assert_eq!(d11.len(), 9);
assert!((d11[0] - 2.0).abs() < 1e-12);
assert!((d11[8] - 2.8).abs() < 1e-12);
assert!(td.block_data(&BlockCoord(vec![0, 1])).is_none());
}
#[test]
#[should_panic(expected = "from_block_fn: closure returned")]
fn from_block_fn_panics_on_wrong_block_length() {
let _: BlockSparseTensorData<f64, U1Sector> = BlockSparseTensorData::from_block_fn(
square_legs(vec![(U1Sector(0), 2)]),
U1Sector(0),
MemoryOrder::RowMajor,
|_, _| vec![0.0; 99],
);
}
#[test]
fn joined_path_pins_arbitrary_order_through_layout_storage_tensor_data() {
use crate::{BlockSparseStorage, TensorData};
for order in [MemoryOrder::RowMajor, MemoryOrder::ColumnMajor] {
let layout: BlockSparseLayout<U1Sector> = BlockSparseLayout::new(
square_legs(vec![(U1Sector(0), 2), (U1Sector(1), 3)]),
U1Sector(0),
order,
);
let extent = TensorLayout::storage_extent(&layout);
assert_eq!(extent, 13);
let data: Vec<f64> = (0..extent).map(|i| i as f64 + 1.0).collect();
let storage = BlockSparseStorage::<f64>::new(data);
let td: BlockSparseTensorData<f64, U1Sector> = TensorData::new(storage, layout);
assert_eq!(td.layout().order(), order);
assert_eq!(td.storage().flat_len(), 13);
let d00 = td.block_data(&BlockCoord(vec![0, 0])).unwrap();
assert_eq!(d00, &[1.0, 2.0, 3.0, 4.0]);
}
}
#[test]
#[should_panic(expected = "storage.flat_len()")]
fn joined_path_rejects_storage_layout_size_mismatch() {
use crate::{BlockSparseStorage, TensorData};
let layout: BlockSparseLayout<U1Sector> = BlockSparseLayout::new(
square_legs(vec![(U1Sector(0), 2)]),
U1Sector(0),
MemoryOrder::RowMajor,
);
let storage = BlockSparseStorage::<f64>::new(vec![0.0; 5]);
let _: BlockSparseTensorData<f64, U1Sector> = TensorData::new(storage, layout);
}