1use std::cmp::Ordering;
2use std::collections::BinaryHeap;
3use std::sync::Arc;
4
5use rustc_hash::{FxHashMap, FxHashSet};
6
7use crate::config::limits::UTILITY;
8use crate::config::selection::selection;
9use crate::interval::IntervalIndex;
10use crate::types::{Fragment, FragmentId};
11use crate::utility::needs::InformationNeed;
12use crate::utility::scoring::{
13 UtilityState, apply_fragment, compute_density, marginal_gain, utility_value,
14};
15
16const SENTINEL_TOKEN_COUNT: u32 = 1_000_000_000;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum SelectionReason {
20 TopK,
21 NoCandidates,
22 BudgetExhausted,
23 NoUtility,
24 StoppedByTau,
25 BestSingleton,
26}
27
28impl SelectionReason {
29 pub fn as_str(&self) -> &'static str {
30 match self {
31 Self::TopK => "topk",
32 Self::NoCandidates => "no_candidates",
33 Self::BudgetExhausted => "budget_exhausted",
34 Self::NoUtility => "no_utility",
35 Self::StoppedByTau => "stopped_by_tau",
36 Self::BestSingleton => "best_singleton",
37 }
38 }
39}
40
41pub struct SelectionResult {
42 pub selected: Vec<Fragment>,
43 pub reason: SelectionReason,
44 pub used_tokens: u32,
45 pub utility: f64,
46 pub greedy_iters: usize,
51 pub stopping_certificate: f64,
56}
57
58struct HeapEntry {
59 neg_density: f64,
60 frag_id: FragmentId,
61 version: u32,
62}
63
64impl PartialEq for HeapEntry {
65 fn eq(&self, other: &Self) -> bool {
66 self.neg_density.to_bits() == other.neg_density.to_bits() && self.frag_id == other.frag_id
67 }
68}
69
70impl Eq for HeapEntry {}
71
72impl PartialOrd for HeapEntry {
73 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
74 Some(self.cmp(other))
75 }
76}
77
78impl Ord for HeapEntry {
79 fn cmp(&self, other: &Self) -> Ordering {
80 other
81 .neg_density
82 .total_cmp(&self.neg_density)
83 .then_with(|| other.frag_id.cmp(&self.frag_id))
84 }
85}
86
87struct SelectionState {
88 selected: Vec<Fragment>,
89 selected_ids: IntervalIndex,
90 remaining_budget: u32,
91 utility_state: UtilityState,
92}
93
94fn drop_redundant_signatures(candidates: &[Fragment], budget: u32) -> Vec<Fragment> {
95 let mut full_token_by_loc: FxHashMap<(Arc<str>, u32), u32> = FxHashMap::default();
96 for f in candidates {
97 if !f.kind.is_signature() {
98 full_token_by_loc.insert((f.id.path.clone(), f.start_line()), f.token_count);
99 }
100 }
101 candidates
102 .iter()
103 .filter(|f| {
104 if !f.kind.is_signature() {
105 return true;
106 }
107 let key = (f.id.path.clone(), f.start_line());
108 full_token_by_loc
109 .get(&key)
110 .copied()
111 .unwrap_or(SENTINEL_TOKEN_COUNT)
112 > budget
113 })
114 .cloned()
115 .collect()
116}
117
118fn compute_r_cap(
119 rel: &FxHashMap<FragmentId, f64>,
120 core_ids: Option<&FxHashSet<FragmentId>>,
121) -> f64 {
122 let values: Vec<f64> = rel
123 .iter()
124 .filter(|(fid, v)| **v > 0.0 && core_ids.map_or(true, |c| !c.contains(*fid)))
125 .map(|(_, v)| *v)
126 .collect();
127
128 if values.len() < 2 {
129 return if let Some(&v) = values.first() {
130 v.max(selection().r_cap_min)
131 } else {
132 1.0
133 };
134 }
135
136 let mut sorted = values.clone();
137 sorted.sort_by(|a, b| a.total_cmp(b));
138 let mid = sorted.len() / 2;
139 let med = if sorted.len() % 2 == 0 {
140 (sorted[mid - 1] + sorted[mid]) / 2.0
141 } else {
142 sorted[mid]
143 };
144
145 let mean: f64 = values.iter().sum::<f64>() / values.len() as f64;
146 let variance: f64 =
147 values.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / (values.len() - 1) as f64;
148 let std = variance.sqrt();
149
150 (med + UTILITY.r_cap_sigma * std).max(1e-9)
151}
152
153fn build_signature_lookup(
154 fragments: &[Fragment],
155 core_fragments: &[Fragment],
156) -> FxHashMap<FragmentId, Fragment> {
157 let mut sig_by_loc: FxHashMap<(Arc<str>, u32), Fragment> = FxHashMap::default();
158 for f in fragments {
159 if f.kind.is_signature() {
160 sig_by_loc.insert((f.id.path.clone(), f.start_line()), f.clone());
161 }
162 }
163 let mut sig_lookup = FxHashMap::default();
164 for cf in core_fragments {
165 let key = (cf.id.path.clone(), cf.start_line());
166 if let Some(sig) = sig_by_loc.get(&key) {
167 sig_lookup.insert(cf.id.clone(), sig.clone());
168 }
169 }
170 sig_lookup
171}
172
173fn select_core_fragments(
174 core_fragments: &[Fragment],
175 rel: &FxHashMap<FragmentId, f64>,
176 needs: &[InformationNeed],
177 state: &mut SelectionState,
178 budget_tokens: u32,
179 sig_lookup: &FxHashMap<FragmentId, Fragment>,
180) {
181 let core_budget = (budget_tokens as f64 * selection().core_budget_fraction) as u32;
182 let mut core_used = 0u32;
186
187 let mut sorted_core: Vec<&Fragment> = core_fragments.iter().collect();
188 sorted_core.sort_by(|a, b| {
189 let ra = rel.get(&a.id).copied().unwrap_or(0.0);
190 let rb = rel.get(&b.id).copied().unwrap_or(0.0);
191 rb.total_cmp(&ra)
192 });
193
194 let place_fragment =
195 |frag: &Fragment, core_used: &mut u32, state: &mut SelectionState, rel_score: f64| {
196 state.selected.push(frag.clone());
197 state.selected_ids.add_id(&frag.id);
198 state.remaining_budget = state.remaining_budget.saturating_sub(frag.token_count);
199 *core_used += frag.token_count;
200 apply_fragment(frag, rel_score, needs, &mut state.utility_state);
201 };
202
203 let mut skipped: Vec<&Fragment> = Vec::new();
204 for frag in &sorted_core {
205 if state.selected_ids.is_superset_of(frag) {
206 continue;
207 }
208 if core_used + frag.token_count > core_budget {
209 if let Some(sig) = sig_lookup.get(&frag.id) {
210 if !state.selected_ids.contains(&sig.id)
211 && core_used + sig.token_count <= core_budget
212 {
213 let rel_score = rel.get(&frag.id).copied().unwrap_or(0.0);
214 place_fragment(sig, &mut core_used, state, rel_score);
215 continue;
216 }
217 }
218 skipped.push(frag);
219 continue;
220 }
221
222 let rel_score = rel.get(&frag.id).copied().unwrap_or(0.0);
223 place_fragment(frag, &mut core_used, state, rel_score);
224 }
225
226 if !skipped.is_empty() {
232 skipped.sort_by(|a, b| a.token_count.cmp(&b.token_count));
233 for frag in skipped {
234 if state.remaining_budget == 0 {
235 break;
236 }
237 if state.selected_ids.is_superset_of(frag) {
238 continue;
239 }
240 if frag.token_count <= state.remaining_budget {
241 let rel_score = rel.get(&frag.id).copied().unwrap_or(0.0);
242 place_fragment(frag, &mut core_used, state, rel_score);
243 } else if let Some(sig) = sig_lookup.get(&frag.id) {
244 if !state.selected_ids.contains(&sig.id)
245 && sig.token_count <= state.remaining_budget
246 {
247 let rel_score = rel.get(&frag.id).copied().unwrap_or(0.0);
248 place_fragment(sig, &mut core_used, state, rel_score);
249 }
250 }
251 }
252 }
253}
254
255fn build_initial_heap(
256 candidates: &[Fragment],
257 rel: &FxHashMap<FragmentId, f64>,
258 needs: &[InformationNeed],
259 state: &UtilityState,
260 id_to_frag: &mut FxHashMap<FragmentId, Fragment>,
261) -> BinaryHeap<HeapEntry> {
262 let mut heap = BinaryHeap::new();
263 for frag in candidates {
264 if frag.token_count > 0 {
265 let density = compute_density(
266 frag,
267 rel.get(&frag.id).copied().unwrap_or(0.0),
268 needs,
269 state,
270 );
271 heap.push(HeapEntry {
272 neg_density: -density,
273 frag_id: frag.id.clone(),
274 version: 0,
275 });
276 id_to_frag.insert(frag.id.clone(), frag.clone());
277 }
278 }
279 heap
280}
281
282fn find_best_candidate_heap(
283 heap: &mut BinaryHeap<HeapEntry>,
284 current_version: u32,
285 id_to_frag: &FxHashMap<FragmentId, Fragment>,
286 selected_ids: &IntervalIndex,
287 remaining_budget: u32,
288 rel: &FxHashMap<FragmentId, f64>,
289 needs: &[InformationNeed],
290 state: &UtilityState,
291) -> (Option<Fragment>, f64, u32) {
292 let cv = current_version;
293 while let Some(entry) = heap.pop() {
294 let frag = match id_to_frag.get(&entry.frag_id) {
295 Some(f) => f,
296 None => continue,
297 };
298 if frag.token_count > remaining_budget {
299 continue;
300 }
301 if selected_ids.overlaps(frag) {
302 continue;
303 }
304 if entry.version < cv {
305 let new_density = compute_density(
306 frag,
307 rel.get(&frag.id).copied().unwrap_or(0.0),
308 needs,
309 state,
310 );
311 heap.push(HeapEntry {
312 neg_density: -new_density,
313 frag_id: frag.id.clone(),
314 version: cv,
315 });
316 continue;
317 }
318 let actual_density = -entry.neg_density;
319 if actual_density <= 0.0 {
320 return (None, 0.0, cv);
321 }
322 return (Some(frag.clone()), actual_density, cv + 1);
323 }
324 (None, 0.0, cv)
325}
326
327fn find_best_singleton(
328 non_core: &[Fragment],
329 base_selected_ids: &IntervalIndex,
330 base_budget: u32,
331 rel: &FxHashMap<FragmentId, f64>,
332 needs: &[InformationNeed],
333 base_state: &UtilityState,
334) -> (Option<Fragment>, f64) {
335 let mut best_singleton = None;
336 let mut best_gain = 0.0;
337 for f in non_core {
338 if f.token_count > base_budget {
339 continue;
340 }
341 if base_selected_ids.overlaps(f) {
342 continue;
343 }
344 let gain = marginal_gain(f, rel.get(&f.id).copied().unwrap_or(0.0), needs, base_state);
345 if gain > best_gain {
346 best_gain = gain;
347 best_singleton = Some(f.clone());
348 }
349 }
350 (best_singleton, best_gain)
351}
352
353fn find_best_singleton_full_set(
365 fragments: &[Fragment],
366 budget_tokens: u32,
367 rel: &FxHashMap<FragmentId, f64>,
368 needs: &[InformationNeed],
369 empty_state: &UtilityState,
370) -> (Option<Fragment>, f64) {
371 let mut best = None;
372 let mut best_gain = 0.0;
373 for f in fragments {
374 if f.token_count == 0 || f.token_count > budget_tokens {
375 continue;
376 }
377 let gain = marginal_gain(
378 f,
379 rel.get(&f.id).copied().unwrap_or(0.0),
380 needs,
381 empty_state,
382 );
383 if gain > best_gain {
384 best_gain = gain;
385 best = Some(f.clone());
386 }
387 }
388 (best, best_gain)
389}
390
391fn init_selection_state(
392 core_ids: &FxHashSet<FragmentId>,
393 rel: &FxHashMap<FragmentId, f64>,
394 budget_tokens: u32,
395 file_importance: Option<&FxHashMap<Arc<str>, f64>>,
396) -> SelectionState {
397 let mut utility_state = UtilityState::default();
398 utility_state.r_cap = compute_r_cap(rel, Some(core_ids));
399 utility_state.changed_dirs = core_ids
400 .iter()
401 .filter_map(|cid| {
402 std::path::Path::new(cid.path.as_ref())
403 .parent()
404 .map(|p| p.to_path_buf())
405 })
406 .collect();
407 if let Some(fi) = file_importance {
408 utility_state.file_importance.clone_from(fi);
409 }
410 SelectionState {
411 selected: Vec::new(),
412 selected_ids: IntervalIndex::new(),
413 remaining_budget: budget_tokens,
414 utility_state,
415 }
416}
417
418fn run_greedy_loop_heap(
419 heap: &mut BinaryHeap<HeapEntry>,
420 id_to_frag: &FxHashMap<FragmentId, Fragment>,
421 state: &mut SelectionState,
422 rel: &FxHashMap<FragmentId, f64>,
423 needs: &[InformationNeed],
424 tau: f64,
425 _initial_budget: u32,
426) -> (usize, f64, usize) {
427 let mut current_version = 0u32;
428 let mut peak_density: f64 = 0.0;
429 let mut loop_iters: usize = 0;
430
431 while !heap.is_empty() && state.remaining_budget > 0 {
432 loop_iters += 1;
433 let (best_frag, best_density, new_version) = find_best_candidate_heap(
434 heap,
435 current_version,
436 id_to_frag,
437 &state.selected_ids,
438 state.remaining_budget,
439 rel,
440 needs,
441 &state.utility_state,
442 );
443 current_version = new_version;
444
445 let best_frag = match best_frag {
446 Some(f) => f,
447 None => break,
448 };
449 if best_density <= 0.0 {
450 break;
451 }
452
453 if best_density > peak_density {
454 peak_density = best_density;
455 } else if peak_density > 0.0 && best_density < tau * peak_density {
456 break;
457 }
458
459 state.selected.push(best_frag.clone());
460 state.selected_ids.add_id(&best_frag.id);
461 state.remaining_budget = state.remaining_budget.saturating_sub(best_frag.token_count);
462 let rel_score = rel.get(&best_frag.id).copied().unwrap_or(0.0);
463 apply_fragment(&best_frag, rel_score, needs, &mut state.utility_state);
464 }
465
466 let threshold = tau * peak_density;
467 (state.selected.len(), threshold, loop_iters)
468}
469
470fn setup_and_select_core(
471 fragments: &[Fragment],
472 core_ids: &FxHashSet<FragmentId>,
473 rel: &FxHashMap<FragmentId, f64>,
474 needs: &[InformationNeed],
475 budget_tokens: u32,
476 file_importance: Option<&FxHashMap<Arc<str>, f64>>,
477) -> (SelectionState, Vec<Fragment>, Vec<Fragment>, bool) {
478 let mut core_fragments: Vec<Fragment> = fragments
479 .iter()
480 .filter(|f| core_ids.contains(&f.id))
481 .cloned()
482 .collect();
483 core_fragments.sort_by(|a, b| {
484 let ta = if a.token_count > 0 {
485 a.token_count
486 } else {
487 SENTINEL_TOKEN_COUNT
488 };
489 let tb = if b.token_count > 0 {
490 b.token_count
491 } else {
492 SENTINEL_TOKEN_COUNT
493 };
494 ta.cmp(&tb)
495 .then(a.line_count().cmp(&b.line_count()))
496 .then(a.start_line().cmp(&b.start_line()))
497 });
498
499 let non_core_fragments: Vec<Fragment> = fragments
500 .iter()
501 .filter(|f| !core_ids.contains(&f.id))
502 .cloned()
503 .collect();
504
505 let sig_lookup = build_signature_lookup(fragments, &core_fragments);
506 let mut state = init_selection_state(core_ids, rel, budget_tokens, file_importance);
507 select_core_fragments(
508 &core_fragments,
509 rel,
510 needs,
511 &mut state,
512 budget_tokens,
513 &sig_lookup,
514 );
515
516 let selected_core_ids: FxHashSet<FragmentId> =
517 state.selected.iter().map(|f| f.id.clone()).collect();
518 let skipped_core: Vec<FragmentId> = core_ids
519 .iter()
520 .filter(|id| !selected_core_ids.contains(*id))
521 .cloned()
522 .collect();
523
524 let mut non_core_with_skipped = non_core_fragments;
525 if !skipped_core.is_empty() {
526 let skipped_set: FxHashSet<FragmentId> = skipped_core.into_iter().collect();
527 for cf in &core_fragments {
528 if skipped_set.contains(&cf.id) {
529 non_core_with_skipped.push(cf.clone());
530 }
531 }
532 }
533
534 let should_return_early = state.remaining_budget == 0;
535 let selected_copy = state.selected.clone();
536 (
537 state,
538 non_core_with_skipped,
539 selected_copy,
540 should_return_early,
541 )
542}
543
544pub fn lazy_greedy_select(
545 fragments: Vec<Fragment>,
546 core_ids: &FxHashSet<FragmentId>,
547 rel: &FxHashMap<FragmentId, f64>,
548 needs: &[InformationNeed],
549 budget_tokens: u32,
550 tau: f64,
551 file_importance: Option<&FxHashMap<Arc<str>, f64>>,
552) -> SelectionResult {
553 if fragments.is_empty() {
554 return SelectionResult {
555 selected: Vec::new(),
556 reason: SelectionReason::NoCandidates,
557 used_tokens: 0,
558 utility: 0.0,
559 greedy_iters: 0,
560 stopping_certificate: 0.0,
561 };
562 }
563
564 let (mut state, non_core_fragments, _selected_core, should_return_early) =
565 setup_and_select_core(
566 &fragments,
567 core_ids,
568 rel,
569 needs,
570 budget_tokens,
571 file_importance,
572 );
573
574 if should_return_early {
575 let used = budget_tokens - state.remaining_budget;
576 return SelectionResult {
577 selected: state.selected,
578 reason: SelectionReason::BudgetExhausted,
579 used_tokens: used,
580 utility: utility_value(&state.utility_state),
581 greedy_iters: 0,
582 stopping_certificate: 0.0,
583 };
584 }
585
586 let base_state = state.utility_state.copy();
587 let base_selected = state.selected.clone();
588 let base_budget = state.remaining_budget;
589
590 let candidates: Vec<Fragment> = non_core_fragments
591 .iter()
592 .filter(|f| !state.selected_ids.overlaps(f))
593 .cloned()
594 .collect();
595 let candidates = drop_redundant_signatures(&candidates, state.remaining_budget);
596
597 let mut id_to_frag: FxHashMap<FragmentId, Fragment> = FxHashMap::default();
598 let mut heap = build_initial_heap(
599 &candidates,
600 rel,
601 needs,
602 &state.utility_state,
603 &mut id_to_frag,
604 );
605
606 let (_, threshold, greedy_iters) = run_greedy_loop_heap(
607 &mut heap,
608 &id_to_frag,
609 &mut state,
610 rel,
611 needs,
612 tau,
613 budget_tokens,
614 );
615
616 let greedy_utility = utility_value(&state.utility_state);
617
618 let mut base_selected_ids = IntervalIndex::new();
619 for f in &base_selected {
620 base_selected_ids.add_id(&f.id);
621 }
622
623 let (best_singleton, best_gain) = find_best_singleton(
624 &non_core_fragments,
625 &base_selected_ids,
626 base_budget,
627 rel,
628 needs,
629 &base_state,
630 );
631
632 let empty_state = init_selection_state(core_ids, rel, budget_tokens, file_importance);
633 let (full_singleton, full_singleton_gain) = find_best_singleton_full_set(
634 &fragments,
635 budget_tokens,
636 rel,
637 needs,
638 &empty_state.utility_state,
639 );
640
641 let mut best_alt_utility = greedy_utility;
642 let mut best_alt: Option<(Vec<Fragment>, u32)> = None;
643
644 if let Some(ref singleton) = best_singleton {
645 let u = utility_value(&base_state) + best_gain;
646 if u > best_alt_utility {
647 best_alt_utility = u;
648 let used = budget_tokens - (base_budget - singleton.token_count);
649 let mut sel = base_selected.clone();
650 sel.push(singleton.clone());
651 best_alt = Some((sel, used));
652 }
653 }
654
655 if let Some(ref full) = full_singleton {
656 let u = utility_value(&empty_state.utility_state) + full_singleton_gain;
657 if u > best_alt_utility {
658 best_alt_utility = u;
659 best_alt = Some((vec![full.clone()], full.token_count));
660 }
661 }
662
663 if let Some((sel, used)) = best_alt {
664 return SelectionResult {
665 selected: sel,
666 reason: SelectionReason::BestSingleton,
667 used_tokens: used,
668 utility: best_alt_utility,
669 greedy_iters,
670 stopping_certificate: 0.0,
671 };
672 }
673
674 let used = budget_tokens - state.remaining_budget;
675 let reason = if state.remaining_budget == 0 {
676 SelectionReason::BudgetExhausted
677 } else if greedy_utility <= 0.0 {
678 SelectionReason::NoUtility
679 } else if state.selected.is_empty() || state.selected.len() == base_selected.len() {
680 SelectionReason::NoCandidates
681 } else if threshold > 0.0 && !heap.is_empty() {
682 SelectionReason::StoppedByTau
683 } else {
684 SelectionReason::NoCandidates
685 };
686
687 let stopping_certificate = if matches!(reason, SelectionReason::StoppedByTau) {
688 threshold * f64::from(state.remaining_budget)
689 } else {
690 0.0
691 };
692
693 SelectionResult {
694 selected: state.selected,
695 reason,
696 used_tokens: used,
697 utility: greedy_utility,
698 greedy_iters,
699 stopping_certificate,
700 }
701}