penrose_memory/
compiler.rs1use crate::cut_and_project::{CutAndProjectCompiler, TileCoord, TileType};
7
8#[derive(Debug, Clone)]
10pub struct AgentTile {
11 pub agent_index: usize,
13 pub tile: TileCoord,
15 pub assignment_distance: f64,
17}
18
19#[derive(Debug, Clone)]
21pub struct FleetTiling {
22 pub source_dim: usize,
24 pub tiles: Vec<TileCoord>,
26 pub assignments: Vec<AgentTile>,
28 pub thick_count: usize,
30 pub thin_count: usize,
32 pub thick_thin_ratio: f64,
34}
35
36pub fn compile_fleet_tiling(agent_embeddings: &[Vec<f64>]) -> FleetTiling {
44 assert!(
45 !agent_embeddings.is_empty(),
46 "Need at least one agent embedding"
47 );
48
49 let source_dim = agent_embeddings[0].len();
50 let target_dim = 2;
51
52 let compiler = CutAndProjectCompiler::new(source_dim, target_dim)
54 .with_pca_projection(agent_embeddings);
55
56 let lattice_range = ((agent_embeddings.len() as f64).sqrt().ceil() as i32).max(3);
58
59 let tiles = compiler.compile(lattice_range);
60
61 let mut assignments = Vec::with_capacity(agent_embeddings.len());
63 for (idx, emb) in agent_embeddings.iter().enumerate() {
64 let mut ax = 0.0f64;
66 let mut ay = 0.0f64;
67 let proj = &compiler.projection();
68 for (r, row) in proj.iter().enumerate() {
69 let mut val = 0.0f64;
70 for (c, &coeff) in row.iter().enumerate() {
71 if c < emb.len() {
72 val += coeff * emb[c];
73 }
74 }
75 if r == 0 {
76 ax = val;
77 } else if r == 1 {
78 ay = val;
79 }
80 }
81
82 let mut best_dist = f64::INFINITY;
84 let mut best_tile_idx = 0usize;
85 for (ti, tile) in tiles.iter().enumerate() {
86 let dx = tile.x - ax;
87 let dy = tile.y - ay;
88 let dist = (dx * dx + dy * dy).sqrt();
89 if dist < best_dist {
90 best_dist = dist;
91 best_tile_idx = ti;
92 }
93 }
94
95 assignments.push(AgentTile {
96 agent_index: idx,
97 tile: tiles[best_tile_idx].clone(),
98 assignment_distance: best_dist,
99 });
100 }
101
102 let thick_count = tiles.iter().filter(|t| t.tile_type == TileType::Thick).count();
103 let thin_count = tiles.iter().filter(|t| t.tile_type == TileType::Thin).count();
104 let thick_thin_ratio = if thin_count > 0 {
105 thick_count as f64 / thin_count as f64
106 } else if thick_count > 0 {
107 f64::INFINITY
108 } else {
109 0.0
110 };
111
112 FleetTiling {
113 source_dim,
114 tiles,
115 assignments,
116 thick_count,
117 thin_count,
118 thick_thin_ratio,
119 }
120}
121
122impl CutAndProjectCompiler {
124 pub fn projection(&self) -> &Vec<Vec<f64>> {
126 &self.projection
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 fn make_agents(n: usize) -> Vec<Vec<f64>> {
136 (0..n)
137 .map(|i| {
138 let t = i as f64 * 0.5;
139 vec![t.cos(), t.sin(), (t * 0.3).cos(), (t * 0.3).sin(), 0.01 * t]
140 })
141 .collect()
142 }
143
144 #[test]
146 fn test_fleet_tiling_compiles() {
147 let agents = make_agents(10);
148 let fleet = compile_fleet_tiling(&agents);
149 assert!(!fleet.tiles.is_empty());
150 assert_eq!(fleet.assignments.len(), 10);
151 }
152
153 #[test]
155 fn test_every_agent_assigned() {
156 let agents = make_agents(20);
157 let fleet = compile_fleet_tiling(&agents);
158 for i in 0..20 {
159 assert!(
160 fleet.assignments.iter().any(|a| a.agent_index == i),
161 "Agent {} should be assigned",
162 i
163 );
164 }
165 }
166
167 #[test]
169 fn test_source_dim_preserved() {
170 let agents = make_agents(5);
171 let fleet = compile_fleet_tiling(&agents);
172 assert_eq!(fleet.source_dim, 5);
173 }
174
175 #[test]
177 fn test_fleet_tile_types_valid() {
178 let agents = make_agents(10);
179 let fleet = compile_fleet_tiling(&agents);
180 for t in &fleet.tiles {
181 assert!(t.tile_type == TileType::Thick || t.tile_type == TileType::Thin);
182 }
183 }
184
185 #[test]
187 fn test_assignment_distances_finite() {
188 let agents = make_agents(15);
189 let fleet = compile_fleet_tiling(&agents);
190 for a in &fleet.assignments {
191 assert!(
192 a.assignment_distance.is_finite(),
193 "Assignment distance should be finite"
194 );
195 }
196 }
197
198 #[test]
200 fn test_thick_thin_sum() {
201 let agents = make_agents(10);
202 let fleet = compile_fleet_tiling(&agents);
203 assert_eq!(
204 fleet.thick_count + fleet.thin_count,
205 fleet.tiles.len(),
206 "Thick + thin should equal total tiles"
207 );
208 }
209
210 #[test]
212 fn test_single_agent() {
213 let agents = vec![vec![1.0, 0.0, 0.0, 0.0, 0.0]];
214 let fleet = compile_fleet_tiling(&agents);
215 assert_eq!(fleet.assignments.len(), 1);
216 assert!(!fleet.tiles.is_empty());
217 }
218
219 #[test]
221 fn test_many_agents() {
222 let agents = make_agents(100);
223 let fleet = compile_fleet_tiling(&agents);
224 assert_eq!(fleet.assignments.len(), 100);
225 assert!(!fleet.tiles.is_empty());
226 }
227}