use crate::algorithms::four_d::{GraphNode4D, GraphProperties, TemporalEdge};
use crate::algorithms::mps::{get_tensor, mps_apply_gate_2site, mps_norm_sq, set_tensor};
#[derive(Clone, Copy, Debug)]
pub struct GridDims {
pub nx: usize,
pub ny: usize,
pub nz: usize,
}
#[derive(Clone, Copy, Debug)]
pub struct SiteCoord {
pub sx: usize,
pub sy: usize,
pub sz: usize,
}
pub fn site_id(sx: usize, sy: usize, sz: usize, tl: usize, nx: usize, ny: usize, nz: usize) -> u64 {
(tl * nz * ny * nx + sz * ny * nx + sy * nx + sx) as u64
}
pub fn build_tnet4d(nx: usize, ny: usize, nz: usize, depth: usize) -> Vec<GraphNode4D> {
let total = nx * ny * nz * depth;
let mut nodes: Vec<GraphNode4D> = Vec::with_capacity(total);
for tl in 0..depth {
for sz in 0..nz {
for sy in 0..ny {
for sx in 0..nx {
let id = site_id(sx, sy, sz, tl, nx, ny, nz);
let mut node = GraphNode4D {
id,
x: sx as f32,
y: sy as f32,
z: sz as f32,
begin_ts: tl as u64,
end_ts: tl as u64 + 1,
properties: GraphProperties::new(),
successors: Vec::new(),
};
set_tensor(&mut node, [1, 2, 1], vec![1.0, 0.0]);
nodes.push(node);
}
}
}
}
let directions: &[(i32, i32, i32)] = &[
(1, 0, 0),
(-1, 0, 0),
(0, 1, 0),
(0, -1, 0),
(0, 0, 1),
(0, 0, -1),
];
for tl in 0..depth {
for sz in 0..nz {
for sy in 0..ny {
for sx in 0..nx {
let src_id = site_id(sx, sy, sz, tl, nx, ny, nz);
for &(dx, dy, dz) in directions {
let nx2 = sx as i32 + dx;
let ny2 = sy as i32 + dy;
let nz2 = sz as i32 + dz;
if nx2 < 0 || nx2 >= nx as i32 {
continue;
}
if ny2 < 0 || ny2 >= ny as i32 {
continue;
}
if nz2 < 0 || nz2 >= nz as i32 {
continue;
}
let dst_id =
site_id(nx2 as usize, ny2 as usize, nz2 as usize, tl, nx, ny, nz);
let src_idx = src_id as usize;
nodes[src_idx].successors.push(TemporalEdge {
dst: dst_id,
weight: 1.0, begin_ts: tl as u64,
end_ts: tl as u64, });
}
}
}
}
}
for tl in 0..depth.saturating_sub(1) {
for sz in 0..nz {
for sy in 0..ny {
for sx in 0..nx {
let src_id = site_id(sx, sy, sz, tl, nx, ny, nz);
let dst_id = site_id(sx, sy, sz, tl + 1, nx, ny, nz);
let src_idx = src_id as usize;
nodes[src_idx].successors.push(TemporalEdge {
dst: dst_id,
weight: 1.0, begin_ts: tl as u64,
end_ts: tl as u64 + 1, });
}
}
}
}
nodes
}
pub fn tnet4d_find(
nodes: &[GraphNode4D],
sx: usize,
sy: usize,
sz: usize,
tl: usize,
dims: GridDims,
) -> Option<usize> {
if sx >= dims.nx || sy >= dims.ny || sz >= dims.nz {
return None;
}
let target_id = site_id(sx, sy, sz, tl, dims.nx, dims.ny, dims.nz);
nodes.iter().position(|n| n.id == target_id)
}
pub fn tnet4d_apply_gate_1site(
nodes: &mut [GraphNode4D],
sx: usize,
sy: usize,
sz: usize,
tl: usize,
dims: GridDims,
gate: &[f32],
) {
if let Some(idx) = tnet4d_find(nodes, sx, sy, sz, tl, dims) {
let (shape, data) = match get_tensor(&nodes[idx]) {
Some(t) => t,
None => return,
};
let (chi_l, d, chi_r) = (shape[0], shape[1], shape[2]);
let mut new_data = vec![0.0f32; chi_l * d * chi_r];
for a in 0..chi_l {
for s_out in 0..d {
for b in 0..chi_r {
let mut val = 0.0f32;
for s_in in 0..d {
val += gate[s_out * d + s_in] * data[a * d * chi_r + s_in * chi_r + b];
}
new_data[a * d * chi_r + s_out * chi_r + b] = val;
}
}
}
set_tensor(&mut nodes[idx], shape, new_data);
}
}
pub fn tnet4d_apply_gate_2site(
nodes: &mut [GraphNode4D],
site0: SiteCoord,
site1: SiteCoord,
tl: usize,
dims: GridDims,
gate: &[f32],
chi_max: usize,
) -> usize {
let idx_a = match tnet4d_find(nodes, site0.sx, site0.sy, site0.sz, tl, dims) {
Some(i) => i,
None => return 0,
};
let idx_b = match tnet4d_find(nodes, site1.sx, site1.sy, site1.sz, tl, dims) {
Some(i) => i,
None => return 0,
};
mps_apply_gate_2site(nodes, idx_a, idx_b, gate, chi_max)
}
pub fn tnet4d_norm_sq(nodes: &[GraphNode4D], nx: usize, ny: usize, nz: usize, depth: usize) -> f64 {
let mut total_norm = 1.0f64;
for sz in 0..nz {
for sy in 0..ny {
for sx in 0..nx {
let dims = GridDims { nx, ny, nz };
let column: Vec<GraphNode4D> = (0..depth)
.filter_map(|tl| {
tnet4d_find(nodes, sx, sy, sz, tl, dims).map(|idx| nodes[idx].clone())
})
.collect();
total_norm *= mps_norm_sq(&column);
}
}
}
total_norm
}
#[cfg(test)]
mod tests {
use super::*;
fn hadamard() -> Vec<f32> {
let s = std::f32::consts::FRAC_1_SQRT_2;
vec![s, s, s, -s]
}
#[test]
fn test_build_node_count_single_layer() {
let nodes = build_tnet4d(2, 2, 1, 1);
assert_eq!(nodes.len(), 4, "2×2×1 grid × 1 layer = 4 nodes");
}
#[test]
fn test_build_node_count_two_layers() {
let nodes = build_tnet4d(2, 2, 1, 2);
assert_eq!(nodes.len(), 8, "2×2×1 grid × 2 layers = 8 nodes");
}
#[test]
fn test_temporal_edges_connect_layers() {
let nodes = build_tnet4d(2, 2, 1, 2);
let src_id = site_id(0, 0, 0, 0, 2, 2, 1);
let dst_id = site_id(0, 0, 0, 1, 2, 2, 1);
let src = nodes.iter().find(|n| n.id == src_id).expect("src node");
let has_temporal = src.successors.iter().any(|e| e.dst == dst_id);
assert!(has_temporal, "no temporal edge from layer 0 to layer 1");
let dst = nodes.iter().find(|n| n.id == dst_id).expect("dst node");
assert!(
dst.begin_ts > src.begin_ts,
"temporal dst should be at a later time layer"
);
}
#[test]
fn test_spatial_edges_connect_neighbours() {
let nodes = build_tnet4d(2, 2, 1, 1);
let src_id = site_id(0, 0, 0, 0, 2, 2, 1);
let right_id = site_id(1, 0, 0, 0, 2, 2, 1);
let up_id = site_id(0, 1, 0, 0, 2, 2, 1);
let src = nodes.iter().find(|n| n.id == src_id).expect("src");
let has_right = src.successors.iter().any(|e| e.dst == right_id);
let has_up = src.successors.iter().any(|e| e.dst == up_id);
assert!(has_right, "missing spatial edge to (1,0,0)");
assert!(has_up, "missing spatial edge to (0,1,0)");
let right = nodes.iter().find(|n| n.id == right_id).expect("right");
assert_eq!(
right.begin_ts, src.begin_ts,
"spatial neighbour must be same layer"
);
}
#[test]
fn test_find_locates_correct_node() {
let nodes = build_tnet4d(2, 2, 1, 2);
let dims = GridDims {
nx: 2,
ny: 2,
nz: 1,
};
let idx = tnet4d_find(&nodes, 1, 1, 0, 0, dims).expect("node must exist");
let expected_id = site_id(1, 1, 0, 0, 2, 2, 1);
assert_eq!(nodes[idx].id, expected_id);
assert!(tnet4d_find(&nodes, 5, 0, 0, 0, dims).is_none());
}
#[test]
fn test_product_state_norm_is_one() {
let nodes = build_tnet4d(2, 2, 1, 1);
let norm = tnet4d_norm_sq(&nodes, 2, 2, 1, 1);
assert!((norm - 1.0).abs() < 1e-5, "norm = {norm}");
}
fn cnot_gate() -> Vec<f32> {
vec![
1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0,
]
}
#[test]
fn test_hadamard_all_sites_preserves_norm() {
let mut nodes = build_tnet4d(2, 2, 1, 1);
let h = hadamard();
let dims = GridDims {
nx: 2,
ny: 2,
nz: 1,
};
for sx in 0..2 {
for sy in 0..2 {
tnet4d_apply_gate_1site(&mut nodes, sx, sy, 0, 0, dims, &h);
}
}
let norm = tnet4d_norm_sq(&nodes, 2, 2, 1, 1);
assert!((norm - 1.0).abs() < 1e-5, "norm after H = {norm}");
}
#[test]
fn test_tnet4d_2site_gate_creates_entanglement() {
let mut nodes = build_tnet4d(2, 2, 1, 1);
let dims = GridDims {
nx: 2,
ny: 2,
nz: 1,
};
tnet4d_apply_gate_1site(&mut nodes, 0, 0, 0, 0, dims, &hadamard());
let site0 = SiteCoord {
sx: 0,
sy: 0,
sz: 0,
};
let site1 = SiteCoord {
sx: 1,
sy: 0,
sz: 0,
};
let chi_new = tnet4d_apply_gate_2site(&mut nodes, site0, site1, 0, dims, &cnot_gate(), 2);
assert_eq!(
chi_new, 2,
"Bell pair between spatial neighbours: bond must be 2"
);
}
}