1use crate::cut_and_project::{TileCoord, TileType};
8
9#[derive(Debug, Clone)]
11pub struct TensorTile {
12 pub position: [f64; 2],
14 pub orientation: f64,
16 pub tile_type: TileType,
18 pub source_coords: [i32; 5],
20 pub tensor: Vec<f32>,
22 pub tensor_shape: (usize, usize),
24}
25
26impl TensorTile {
27 pub fn new(
32 source_coords: [i32; 5],
33 tile_type: TileType,
34 orientation: f64,
35 position: [f64; 2],
36 ) -> Self {
37 let shape = match tile_type {
38 TileType::Thick => (5, 5),
39 TileType::Thin => (3, 8),
40 TileType::Rejected => (1, 1), };
42 let len = shape.0 * shape.1;
43 Self {
44 position,
45 orientation,
46 tile_type,
47 source_coords,
48 tensor: vec![0.0f32; len],
49 tensor_shape: shape,
50 }
51 }
52
53 pub fn fill_from_source(&mut self) {
64 let (rows, cols) = self.tensor_shape;
65 let src = self.source_coords;
66
67 let a = (src[0].unsigned_abs() as f32).max(1.0) / 100.0;
69 let b = src[1] as f32;
70 let c = (src[2].unsigned_abs() as f32).max(1.0);
71 let d = src[3].unsigned_abs() as u32;
72 let e = src[4] as f32;
73
74 for i in 0..rows {
75 for j in 0..cols {
76 let idx = i * cols + j;
77 let i_f = i as f32;
78 let j_f = j as f32;
79 let m = rows as f32;
80 let n = cols as f32;
81
82 let mode_a = a * (src[0].unsigned_abs() as f32 + 1.0) / 10.0;
84
85 let mode_b = b * i_f / m;
87
88 let mode_c = (2.0 * std::f32::consts::PI * c * j_f / n).sin();
90
91 let hash = Self::simple_hash(i as u32, j as u32, d);
93 let mode_d = (hash as f32) / (u32::MAX as f32);
94
95 let mode_e = (2.0 * std::f32::consts::PI * c * i_f / m + e / 10.0).sin();
97
98 self.tensor[idx] = mode_a + mode_b + mode_c + mode_d + mode_e;
100 }
101 }
102 }
103
104 fn simple_hash(i: u32, j: u32, seed: u32) -> u32 {
106 let mut h = 2166136261u32;
108 h ^= i.wrapping_mul(seed.wrapping_add(1));
109 h = h.wrapping_mul(16777619);
110 h ^= j.wrapping_mul(seed.wrapping_add(7));
111 h = h.wrapping_mul(16777619);
112 h ^= seed;
113 h = h.wrapping_mul(16777619);
114 h
115 }
116
117 pub fn apply_threshold(&mut self, threshold: f32) {
119 for v in &mut self.tensor {
120 if *v < threshold {
121 *v = 0.0;
122 }
123 }
124 }
125
126 pub fn tensor_at(&self, row: usize, col: usize) -> f32 {
128 let (rows, cols) = self.tensor_shape;
129 assert!(row < rows && col < cols, "index out of bounds");
130 self.tensor[row * cols + col]
131 }
132
133 pub fn l1_norm(&self) -> f32 {
135 self.tensor.iter().map(|v| v.abs()).sum()
136 }
137
138 pub fn l2_norm(&self) -> f32 {
140 let sum_sq: f32 = self.tensor.iter().map(|v| v * v).sum();
141 sum_sq.sqrt()
142 }
143
144 pub fn tensor_len(&self) -> usize {
146 self.tensor_shape.0 * self.tensor_shape.1
147 }
148}
149
150#[derive(Debug, Clone)]
156pub struct TensorTiling {
157 pub tiles: Vec<TensorTile>,
158 pub adjacency: Vec<(usize, usize, f64)>,
160}
161
162impl TensorTiling {
163 pub fn new(tiles: Vec<TensorTile>) -> Self {
168 let adjacency = Self::detect_adjacency(&tiles);
169 Self { tiles, adjacency }
170 }
171
172 fn detect_adjacency(tiles: &[TensorTile]) -> Vec<(usize, usize, f64)> {
174 let threshold = 2.0;
175 let threshold_sq = threshold * threshold;
176 let mut edges = Vec::new();
177
178 for i in 0..tiles.len() {
179 for j in (i + 1)..tiles.len() {
180 let dx = tiles[i].position[0] - tiles[j].position[0];
181 let dy = tiles[i].position[1] - tiles[j].position[1];
182 let dist_sq = dx * dx + dy * dy;
183 if dist_sq < threshold_sq {
184 let orientation = dy.atan2(dx);
186 edges.push((i, j, orientation));
187 }
188 }
189 }
190 edges
191 }
192
193 pub fn apply_kernel<F>(&mut self, f: F)
195 where
196 F: Fn(&mut TensorTile),
197 {
198 for tile in &mut self.tiles {
199 f(tile);
200 }
201 }
202
203 pub fn constraint_check(&self) -> f32 {
209 let mut total_mismatch = 0.0f32;
210
211 for &(i, j, _orientation) in &self.adjacency {
212 let tile_a = &self.tiles[i];
213 let tile_b = &self.tiles[j];
214 let (rows_a, cols_a) = tile_a.tensor_shape;
215 let (rows_b, cols_b) = tile_b.tensor_shape;
216
217 let border_len = cols_a.min(cols_b);
219
220 for col in 0..border_len {
222 let va = tile_a.tensor_at(rows_a - 1, col.min(cols_a - 1));
223 let vb = tile_b.tensor_at(0, col.min(cols_b - 1));
224 total_mismatch += (va - vb).abs();
225 }
226
227 let col_border_len = rows_a.min(rows_b);
229 for row in 0..col_border_len {
230 let va = tile_a.tensor_at(row.min(rows_a - 1), cols_a - 1);
231 let vb = tile_b.tensor_at(row.min(rows_b - 1), 0);
232 total_mismatch += (va - vb).abs();
233 }
234 }
235
236 total_mismatch
237 }
238}
239
240pub fn generate_tensor_tiling(
249 _lattice_points: &[[i32; 5]],
250 baseline_tiles: &[TileCoord],
251) -> TensorTiling {
252 let mut tensor_tiles = Vec::with_capacity(baseline_tiles.len());
253
254 for tc in baseline_tiles {
255 let mut coords = [0i32; 5];
257 for (k, &v) in tc.source_coords.iter().enumerate().take(5) {
258 coords[k] = v;
259 }
260
261 let mut tt = TensorTile::new(
262 coords,
263 tc.tile_type,
264 0.0, [tc.x, tc.y],
266 );
267 tt.fill_from_source();
268 tensor_tiles.push(tt);
269 }
270
271 TensorTiling::new(tensor_tiles)
272}
273
274#[cfg(test)]
279mod tests {
280 use super::*;
281 use crate::cut_and_project::{CutAndProjectCompiler, TileType};
282
283 #[test]
284 fn test_tensor_tile_creation() {
285 let tile = TensorTile::new(
286 [1, 2, 3, 4, 5],
287 TileType::Thick,
288 0.0,
289 [1.0, 2.0],
290 );
291 assert_eq!(tile.tensor_shape, (5, 5));
292 assert_eq!(tile.tensor.len(), 25);
293 assert!(tile.tensor.iter().all(|&v| v == 0.0));
295 }
296
297 #[test]
298 fn test_tensor_tile_creation_thin() {
299 let tile = TensorTile::new(
300 [1, 2, 3, 4, 5],
301 TileType::Thin,
302 0.0,
303 [0.0, 0.0],
304 );
305 assert_eq!(tile.tensor_shape, (3, 8));
306 assert_eq!(tile.tensor.len(), 24);
307 }
308
309 #[test]
310 fn test_tensor_tile_fill_modes() {
311 let bases: [[i32; 5]; 5] = [
314 [10, 0, 0, 0, 0],
315 [0, 10, 0, 0, 0],
316 [0, 0, 10, 0, 0],
317 [0, 0, 0, 10, 0],
318 [0, 0, 0, 0, 10],
319 ];
320
321 let filled: Vec<Vec<f32>> = bases
322 .iter()
323 .map(|&coords| {
324 let mut tile =
325 TensorTile::new(coords, TileType::Thick, 0.0, [0.0, 0.0]);
326 tile.fill_from_source();
327 tile.tensor.clone()
328 })
329 .collect();
330
331 for i in 0..filled.len() {
333 for j in (i + 1)..filled.len() {
334 assert_ne!(
335 filled[i], filled[j],
336 "Fill modes {} and {} produced identical tensors",
337 i, j
338 );
339 }
340 }
341 }
342
343 #[test]
344 fn test_threshold_filter() {
345 let mut tile = TensorTile::new(
346 [5, 5, 5, 5, 5],
347 TileType::Thick,
348 0.0,
349 [0.0, 0.0],
350 );
351 tile.fill_from_source();
352
353 let before = tile.tensor.clone();
355 tile.apply_threshold(0.5);
356
357 for (idx, &v) in tile.tensor.iter().enumerate() {
359 if before[idx] < 0.5 {
360 assert_eq!(v, 0.0, "value {} was below threshold but not zeroed", before[idx]);
361 } else {
362 assert_eq!(v, before[idx], "value {} was above threshold but changed", before[idx]);
363 }
364 }
365 }
366
367 #[test]
368 fn test_tiling_adjacency() {
369 let tiles = vec![
371 TensorTile::new([1, 0, 0, 0, 0], TileType::Thick, 0.0, [0.0, 0.0]),
372 TensorTile::new([0, 1, 0, 0, 0], TileType::Thick, 0.0, [0.5, 0.5]),
373 ];
374 let tiling = TensorTiling::new(tiles);
375 assert!(
376 !tiling.adjacency.is_empty(),
377 "Two close tiles should detect adjacency"
378 );
379 }
380
381 #[test]
382 fn test_tiling_no_adjacency_far_tiles() {
383 let tiles = vec![
385 TensorTile::new([1, 0, 0, 0, 0], TileType::Thick, 0.0, [0.0, 0.0]),
386 TensorTile::new([0, 1, 0, 0, 0], TileType::Thick, 0.0, [100.0, 100.0]),
387 ];
388 let tiling = TensorTiling::new(tiles);
389 assert!(
390 tiling.adjacency.is_empty(),
391 "Far tiles should not be adjacent"
392 );
393 }
394
395 #[test]
396 fn test_constraint_check_identical_vs_different() {
397 let mut tile_a =
399 TensorTile::new([2, 2, 2, 2, 2], TileType::Thick, 0.0, [0.0, 0.0]);
400 tile_a.fill_from_source();
401
402 let mut tile_b_identical = tile_a.clone();
404 tile_b_identical.position = [0.5, 0.5]; let tiling_identical = TensorTiling::new(vec![tile_a.clone(), tile_b_identical]);
407 let mismatch_identical = tiling_identical.constraint_check();
408
409 let mut tile_c =
411 TensorTile::new([50, 50, 50, 50, 50], TileType::Thick, 0.0, [0.0, 0.0]);
412 tile_c.fill_from_source();
413 let mut tile_d = tile_c.clone();
414 tile_d.position = [0.5, 0.5];
415
416 let tiling_different = TensorTiling::new(vec![tile_a, tile_d]);
417 let mismatch_different = tiling_different.constraint_check();
418
419 assert!(
421 mismatch_different >= mismatch_identical,
422 "Different tiles (mismatch={}) should have >= mismatch than identical ({})",
423 mismatch_different,
424 mismatch_identical,
425 );
426 }
427
428 #[test]
429 fn test_norms() {
430 let mut tile =
431 TensorTile::new([3, 3, 3, 3, 3], TileType::Thick, 0.0, [0.0, 0.0]);
432 tile.fill_from_source();
433 let l1 = tile.l1_norm();
434 let l2 = tile.l2_norm();
435 assert!(l1 > 0.0, "L1 norm should be positive after fill");
436 assert!(l2 > 0.0, "L2 norm should be positive after fill");
437 assert!(l1 >= l2, "L1 ({}) should be >= L2 ({})", l1, l2);
439 }
440
441 #[test]
442 fn test_generate_tensor_tiling() {
443 let compiler = CutAndProjectCompiler::new(5, 2).with_golden_projection();
444 let baseline = compiler.compile(2);
445 if baseline.is_empty() {
446 return; }
448 let lattice: Vec<[i32; 5]> = baseline
449 .iter()
450 .map(|tc| {
451 let mut c = [0i32; 5];
452 for (k, &v) in tc.source_coords.iter().enumerate().take(5) {
453 c[k] = v;
454 }
455 c
456 })
457 .collect();
458 let tiling = generate_tensor_tiling(&lattice, &baseline);
459 assert_eq!(tiling.tiles.len(), baseline.len());
460 let any_filled = tiling
462 .tiles
463 .iter()
464 .any(|t| t.tensor.iter().any(|&v| v != 0.0));
465 assert!(any_filled, "At least one tensor should have non-zero values");
466 }
467}