1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub struct RopeBlock {
11 pub block_index: usize,
13 pub dim_start: usize,
15 pub dim_end: usize,
17}
18
19pub fn rope_blocks(head_dim: usize) -> Vec<RopeBlock> {
25 (0..head_dim / 2)
26 .map(|k| RopeBlock {
27 block_index: k,
28 dim_start: 2 * k,
29 dim_end: 2 * k + 2,
30 })
31 .collect()
32}
33
34#[derive(Debug, Clone, Copy)]
36pub struct RopeBlockEnergy {
37 pub block: RopeBlock,
38 pub energy: f32,
40}
41
42pub fn rope_block_energies(keys: &[Vec<f32>], head_dim: usize) -> Vec<RopeBlockEnergy> {
49 rope_blocks(head_dim)
50 .into_iter()
51 .map(|block| {
52 let mut sum_sq = 0.0f64;
53 let mut count = 0usize;
54 for key in keys {
55 if key.len() < block.dim_end {
56 continue;
57 }
58 for &v in &key[block.dim_start..block.dim_end] {
59 sum_sq += (v as f64) * (v as f64);
60 }
61 count += 1;
62 }
63 let energy = if count == 0 {
64 0.0f32
65 } else {
66 (sum_sq / ((count * 2) as f64)) as f32
67 };
68 RopeBlockEnergy { block, energy }
69 })
70 .collect()
71}
72
73#[derive(Debug, Clone)]
75pub struct RopeBitAllocation {
76 pub bits_per_block: Vec<u8>,
78 pub total_bits: usize,
80}
81
82pub fn allocate_rope_bits(
95 energies: &[RopeBlockEnergy],
96 min_bits: u8,
97 max_bits: u8,
98 total_bits: usize,
99) -> RopeBitAllocation {
100 let n = energies.len();
101 if n == 0 {
102 return RopeBitAllocation {
103 bits_per_block: vec![],
104 total_bits: 0,
105 };
106 }
107
108 let effective_max = max_bits.max(min_bits);
109 let mut alloc: Vec<u8> = vec![min_bits; n];
110 let min_total = (min_bits as usize) * n;
111
112 if total_bits <= min_total {
113 return RopeBitAllocation {
114 bits_per_block: alloc,
115 total_bits: min_total,
116 };
117 }
118
119 let mut remaining = total_bits - min_total;
120 while remaining > 0 {
121 let best = energies
124 .iter()
125 .enumerate()
126 .filter(|(i, _)| alloc[*i] < effective_max)
127 .max_by(|(ia, a), (ib, b)| {
128 a.energy
129 .partial_cmp(&b.energy)
130 .unwrap_or(std::cmp::Ordering::Equal)
131 .then(ib.cmp(ia)) });
133 match best {
134 None => break, Some((idx, _)) => {
136 alloc[idx] += 1;
137 remaining -= 1;
138 }
139 }
140 }
141
142 let actual_total: usize = alloc.iter().map(|&b| b as usize).sum();
143 RopeBitAllocation {
144 bits_per_block: alloc,
145 total_bits: actual_total,
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 #[test]
154 fn rope_blocks_even_head_dim_returns_2d_spans() {
155 let blocks = rope_blocks(8);
156 assert_eq!(blocks.len(), 4);
157 assert_eq!(
158 blocks[0],
159 RopeBlock {
160 block_index: 0,
161 dim_start: 0,
162 dim_end: 2
163 }
164 );
165 assert_eq!(
166 blocks[1],
167 RopeBlock {
168 block_index: 1,
169 dim_start: 2,
170 dim_end: 4
171 }
172 );
173 assert_eq!(
174 blocks[2],
175 RopeBlock {
176 block_index: 2,
177 dim_start: 4,
178 dim_end: 6
179 }
180 );
181 assert_eq!(
182 blocks[3],
183 RopeBlock {
184 block_index: 3,
185 dim_start: 6,
186 dim_end: 8
187 }
188 );
189 }
190
191 #[test]
192 fn rope_blocks_odd_head_dim_ignores_trailing_dim() {
193 let blocks = rope_blocks(9);
195 assert_eq!(blocks.len(), 4);
196 assert_eq!(blocks.last().unwrap().dim_end, 8);
197 }
198
199 #[test]
200 fn rope_block_energies_rank_high_energy_block_first() {
201 let keys = vec![vec![0.0f32, 0.0, 10.0, 10.0], vec![0.0f32, 0.0, 10.0, 10.0]];
203 let energies = rope_block_energies(&keys, 4);
204 assert_eq!(energies.len(), 2);
205 assert!(energies[1].energy > energies[0].energy);
206 assert_eq!(energies[0].energy, 0.0);
207 assert!((energies[1].energy - 100.0f32).abs() < 1e-4);
208 }
209
210 #[test]
211 fn rope_block_energies_empty_keys_are_zero() {
212 let energies = rope_block_energies(&[], 8);
213 assert_eq!(energies.len(), 4);
214 for be in &energies {
215 assert_eq!(be.energy, 0.0);
216 }
217 }
218
219 #[test]
220 fn allocate_rope_bits_honors_min_max_and_budget() {
221 let blocks = rope_blocks(8);
223 let energies: Vec<RopeBlockEnergy> = blocks
224 .iter()
225 .map(|&block| RopeBlockEnergy { block, energy: 1.0 })
226 .collect();
227 let alloc = allocate_rope_bits(&energies, 2, 4, 12);
228 assert_eq!(alloc.total_bits, 12);
229 for &b in &alloc.bits_per_block {
230 assert!((2..=4).contains(&b), "got {b}");
231 }
232 }
233
234 #[test]
235 fn allocate_rope_bits_prefers_high_energy_blocks() {
236 let blocks = rope_blocks(8);
237 let energies: Vec<RopeBlockEnergy> = blocks
238 .iter()
239 .enumerate()
240 .map(|(i, &block)| RopeBlockEnergy {
241 block,
242 energy: if i == 3 { 100.0 } else { 1.0 },
243 })
244 .collect();
245 let alloc = allocate_rope_bits(&energies, 2, 8, 2 * 4 + 1);
247 assert_eq!(
248 alloc.bits_per_block[3], 3,
249 "extra bit must go to highest-energy block"
250 );
251 assert_eq!(alloc.bits_per_block[0], 2);
252 }
253
254 #[test]
255 fn allocate_rope_bits_tie_breaks_by_block_index() {
256 let blocks = rope_blocks(8);
257 let energies: Vec<RopeBlockEnergy> = blocks
259 .iter()
260 .map(|&block| RopeBlockEnergy { block, energy: 5.0 })
261 .collect();
262 let alloc = allocate_rope_bits(&energies, 2, 8, 2 * 4 + 1);
264 assert_eq!(
265 alloc.bits_per_block[0], 3,
266 "tie must break to lower block_index"
267 );
268 assert_eq!(alloc.bits_per_block[1], 2);
269 assert_eq!(alloc.bits_per_block[2], 2);
270 assert_eq!(alloc.bits_per_block[3], 2);
271 }
272}