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 core_excerpts: Option<&FxHashMap<FragmentId, Fragment>>,
157) -> FxHashMap<FragmentId, Fragment> {
158 let mut sig_by_loc: FxHashMap<(Arc<str>, u32), Fragment> = FxHashMap::default();
159 for f in fragments {
160 if f.kind.is_signature() {
161 sig_by_loc.insert((f.id.path.clone(), f.start_line()), f.clone());
162 }
163 }
164 let mut sig_lookup = FxHashMap::default();
165 for cf in core_fragments {
166 let key = (cf.id.path.clone(), cf.start_line());
167 if let Some(sig) = sig_by_loc.get(&key) {
168 sig_lookup.insert(cf.id.clone(), sig.clone());
169 continue;
170 }
171 if let Some(excerpt) = core_excerpts.and_then(|e| e.get(&cf.id)) {
175 sig_lookup.insert(cf.id.clone(), excerpt.clone());
176 }
177 }
178 sig_lookup
179}
180
181fn select_core_fragments(
182 core_fragments: &[Fragment],
183 rel: &FxHashMap<FragmentId, f64>,
184 needs: &[InformationNeed],
185 state: &mut SelectionState,
186 budget_tokens: u32,
187 sig_lookup: &FxHashMap<FragmentId, Fragment>,
188) {
189 let core_budget = (budget_tokens as f64 * selection().core_budget_fraction) as u32;
190 let mut core_used = 0u32;
194
195 let mut sorted_core: Vec<&Fragment> = core_fragments.iter().collect();
196 sorted_core.sort_by(|a, b| {
197 let ra = rel.get(&a.id).copied().unwrap_or(0.0);
198 let rb = rel.get(&b.id).copied().unwrap_or(0.0);
199 rb.total_cmp(&ra)
200 });
201
202 let place_fragment =
203 |frag: &Fragment, core_used: &mut u32, state: &mut SelectionState, rel_score: f64| {
204 state.selected.push(frag.clone());
205 state.selected_ids.add_id(&frag.id);
206 state.remaining_budget = state.remaining_budget.saturating_sub(frag.token_count);
207 *core_used += frag.token_count;
208 apply_fragment(frag, rel_score, needs, &mut state.utility_state);
209 };
210
211 let mut skipped: Vec<&Fragment> = Vec::new();
212 for frag in &sorted_core {
213 if state.selected_ids.is_superset_of(frag) {
214 continue;
215 }
216 if core_used + frag.token_count > core_budget {
217 if let Some(sig) = sig_lookup.get(&frag.id) {
218 if !state.selected_ids.contains(&sig.id)
219 && core_used + sig.token_count <= core_budget
220 {
221 let rel_score = rel.get(&frag.id).copied().unwrap_or(0.0);
222 place_fragment(sig, &mut core_used, state, rel_score);
223 continue;
224 }
225 }
226 skipped.push(frag);
227 continue;
228 }
229
230 let rel_score = rel.get(&frag.id).copied().unwrap_or(0.0);
231 place_fragment(frag, &mut core_used, state, rel_score);
232 }
233
234 if !skipped.is_empty() {
240 skipped.sort_by(|a, b| a.token_count.cmp(&b.token_count));
241 for frag in skipped {
242 if state.remaining_budget == 0 {
243 break;
244 }
245 if state.selected_ids.is_superset_of(frag) {
246 continue;
247 }
248 if frag.token_count <= state.remaining_budget {
249 let rel_score = rel.get(&frag.id).copied().unwrap_or(0.0);
250 place_fragment(frag, &mut core_used, state, rel_score);
251 } else if let Some(sig) = sig_lookup.get(&frag.id) {
252 if !state.selected_ids.contains(&sig.id)
253 && sig.token_count <= state.remaining_budget
254 {
255 let rel_score = rel.get(&frag.id).copied().unwrap_or(0.0);
256 place_fragment(sig, &mut core_used, state, rel_score);
257 }
258 }
259 }
260 }
261}
262
263fn build_initial_heap(
264 candidates: &[Fragment],
265 rel: &FxHashMap<FragmentId, f64>,
266 needs: &[InformationNeed],
267 state: &UtilityState,
268 id_to_frag: &mut FxHashMap<FragmentId, Fragment>,
269) -> BinaryHeap<HeapEntry> {
270 let mut heap = BinaryHeap::new();
271 for frag in candidates {
272 if frag.token_count > 0 {
273 let density = compute_density(
274 frag,
275 rel.get(&frag.id).copied().unwrap_or(0.0),
276 needs,
277 state,
278 );
279 heap.push(HeapEntry {
280 neg_density: -density,
281 frag_id: frag.id.clone(),
282 version: 0,
283 });
284 id_to_frag.insert(frag.id.clone(), frag.clone());
285 }
286 }
287 heap
288}
289
290fn find_best_candidate_heap(
291 heap: &mut BinaryHeap<HeapEntry>,
292 current_version: u32,
293 id_to_frag: &FxHashMap<FragmentId, Fragment>,
294 selected_ids: &IntervalIndex,
295 remaining_budget: u32,
296 rel: &FxHashMap<FragmentId, f64>,
297 needs: &[InformationNeed],
298 state: &UtilityState,
299) -> (Option<Fragment>, f64, u32) {
300 let cv = current_version;
301 while let Some(entry) = heap.pop() {
302 let frag = match id_to_frag.get(&entry.frag_id) {
303 Some(f) => f,
304 None => continue,
305 };
306 if frag.token_count > remaining_budget {
307 continue;
308 }
309 if selected_ids.overlaps(frag) {
310 continue;
311 }
312 if entry.version < cv {
313 let new_density = compute_density(
314 frag,
315 rel.get(&frag.id).copied().unwrap_or(0.0),
316 needs,
317 state,
318 );
319 heap.push(HeapEntry {
320 neg_density: -new_density,
321 frag_id: frag.id.clone(),
322 version: cv,
323 });
324 continue;
325 }
326 let actual_density = -entry.neg_density;
327 if actual_density <= 0.0 {
328 return (None, 0.0, cv);
329 }
330 return (Some(frag.clone()), actual_density, cv + 1);
331 }
332 (None, 0.0, cv)
333}
334
335fn find_best_singleton(
336 non_core: &[Fragment],
337 base_selected_ids: &IntervalIndex,
338 base_budget: u32,
339 rel: &FxHashMap<FragmentId, f64>,
340 needs: &[InformationNeed],
341 base_state: &UtilityState,
342) -> (Option<Fragment>, f64) {
343 let mut best_singleton = None;
344 let mut best_gain = 0.0;
345 for f in non_core {
346 if f.token_count > base_budget {
347 continue;
348 }
349 if base_selected_ids.overlaps(f) {
350 continue;
351 }
352 let gain = marginal_gain(f, rel.get(&f.id).copied().unwrap_or(0.0), needs, base_state);
353 if gain > best_gain {
354 best_gain = gain;
355 best_singleton = Some(f.clone());
356 }
357 }
358 (best_singleton, best_gain)
359}
360
361fn find_best_singleton_full_set(
373 fragments: &[Fragment],
374 budget_tokens: u32,
375 rel: &FxHashMap<FragmentId, f64>,
376 needs: &[InformationNeed],
377 empty_state: &UtilityState,
378) -> (Option<Fragment>, f64) {
379 let mut best = None;
380 let mut best_gain = 0.0;
381 for f in fragments {
382 if f.token_count == 0 || f.token_count > budget_tokens {
383 continue;
384 }
385 let gain = marginal_gain(
386 f,
387 rel.get(&f.id).copied().unwrap_or(0.0),
388 needs,
389 empty_state,
390 );
391 if gain > best_gain {
392 best_gain = gain;
393 best = Some(f.clone());
394 }
395 }
396 (best, best_gain)
397}
398
399fn init_selection_state(
400 core_ids: &FxHashSet<FragmentId>,
401 rel: &FxHashMap<FragmentId, f64>,
402 budget_tokens: u32,
403 file_importance: Option<&FxHashMap<Arc<str>, f64>>,
404) -> SelectionState {
405 let mut utility_state = UtilityState::default();
406 utility_state.r_cap = compute_r_cap(rel, Some(core_ids));
407 utility_state.changed_dirs = core_ids
408 .iter()
409 .filter_map(|cid| {
410 std::path::Path::new(cid.path.as_ref())
411 .parent()
412 .map(|p| p.to_path_buf())
413 })
414 .collect();
415 if let Some(fi) = file_importance {
416 utility_state.file_importance.clone_from(fi);
417 }
418 SelectionState {
419 selected: Vec::new(),
420 selected_ids: IntervalIndex::new(),
421 remaining_budget: budget_tokens,
422 utility_state,
423 }
424}
425
426fn run_greedy_loop_heap(
427 heap: &mut BinaryHeap<HeapEntry>,
428 id_to_frag: &FxHashMap<FragmentId, Fragment>,
429 state: &mut SelectionState,
430 rel: &FxHashMap<FragmentId, f64>,
431 needs: &[InformationNeed],
432 tau: f64,
433 _initial_budget: u32,
434) -> (usize, f64, usize) {
435 let mut current_version = 0u32;
436 let mut peak_density: f64 = 0.0;
437 let mut loop_iters: usize = 0;
438
439 while !heap.is_empty() && state.remaining_budget > 0 {
440 loop_iters += 1;
441 let (best_frag, best_density, new_version) = find_best_candidate_heap(
442 heap,
443 current_version,
444 id_to_frag,
445 &state.selected_ids,
446 state.remaining_budget,
447 rel,
448 needs,
449 &state.utility_state,
450 );
451 current_version = new_version;
452
453 let best_frag = match best_frag {
454 Some(f) => f,
455 None => break,
456 };
457 if best_density <= 0.0 {
458 break;
459 }
460
461 if best_density > peak_density {
462 peak_density = best_density;
463 } else if peak_density > 0.0 && best_density < tau * peak_density {
464 break;
465 }
466
467 state.selected.push(best_frag.clone());
468 state.selected_ids.add_id(&best_frag.id);
469 state.remaining_budget = state.remaining_budget.saturating_sub(best_frag.token_count);
470 let rel_score = rel.get(&best_frag.id).copied().unwrap_or(0.0);
471 apply_fragment(&best_frag, rel_score, needs, &mut state.utility_state);
472 }
473
474 let threshold = tau * peak_density;
475 (state.selected.len(), threshold, loop_iters)
476}
477
478fn setup_and_select_core(
479 fragments: &[Fragment],
480 core_ids: &FxHashSet<FragmentId>,
481 rel: &FxHashMap<FragmentId, f64>,
482 needs: &[InformationNeed],
483 budget_tokens: u32,
484 file_importance: Option<&FxHashMap<Arc<str>, f64>>,
485 core_excerpts: Option<&FxHashMap<FragmentId, Fragment>>,
486) -> (SelectionState, Vec<Fragment>, Vec<Fragment>, bool) {
487 let mut core_fragments: Vec<Fragment> = fragments
488 .iter()
489 .filter(|f| core_ids.contains(&f.id))
490 .cloned()
491 .collect();
492 core_fragments.sort_by(|a, b| {
493 let ta = if a.token_count > 0 {
494 a.token_count
495 } else {
496 SENTINEL_TOKEN_COUNT
497 };
498 let tb = if b.token_count > 0 {
499 b.token_count
500 } else {
501 SENTINEL_TOKEN_COUNT
502 };
503 ta.cmp(&tb)
504 .then(a.line_count().cmp(&b.line_count()))
505 .then(a.start_line().cmp(&b.start_line()))
506 });
507
508 let non_core_fragments: Vec<Fragment> = fragments
509 .iter()
510 .filter(|f| !core_ids.contains(&f.id))
511 .cloned()
512 .collect();
513
514 let sig_lookup = build_signature_lookup(fragments, &core_fragments, core_excerpts);
515 let mut state = init_selection_state(core_ids, rel, budget_tokens, file_importance);
516 select_core_fragments(
517 &core_fragments,
518 rel,
519 needs,
520 &mut state,
521 budget_tokens,
522 &sig_lookup,
523 );
524
525 let selected_core_ids: FxHashSet<FragmentId> =
526 state.selected.iter().map(|f| f.id.clone()).collect();
527 let skipped_core: Vec<FragmentId> = core_ids
528 .iter()
529 .filter(|id| !selected_core_ids.contains(*id))
530 .cloned()
531 .collect();
532
533 let mut non_core_with_skipped = non_core_fragments;
534 if !skipped_core.is_empty() {
535 let skipped_set: FxHashSet<FragmentId> = skipped_core.into_iter().collect();
536 for cf in &core_fragments {
537 if skipped_set.contains(&cf.id) {
538 non_core_with_skipped.push(cf.clone());
539 }
540 }
541 }
542
543 let should_return_early = state.remaining_budget == 0;
544 let selected_copy = state.selected.clone();
545 (
546 state,
547 non_core_with_skipped,
548 selected_copy,
549 should_return_early,
550 )
551}
552
553pub fn lazy_greedy_select(
554 fragments: Vec<Fragment>,
555 core_ids: &FxHashSet<FragmentId>,
556 rel: &FxHashMap<FragmentId, f64>,
557 needs: &[InformationNeed],
558 budget_tokens: u32,
559 tau: f64,
560 file_importance: Option<&FxHashMap<Arc<str>, f64>>,
561 core_excerpts: Option<&FxHashMap<FragmentId, Fragment>>,
562) -> SelectionResult {
563 if fragments.is_empty() {
564 return SelectionResult {
565 selected: Vec::new(),
566 reason: SelectionReason::NoCandidates,
567 used_tokens: 0,
568 utility: 0.0,
569 greedy_iters: 0,
570 stopping_certificate: 0.0,
571 };
572 }
573
574 let (mut state, non_core_fragments, _selected_core, should_return_early) =
575 setup_and_select_core(
576 &fragments,
577 core_ids,
578 rel,
579 needs,
580 budget_tokens,
581 file_importance,
582 core_excerpts,
583 );
584
585 if should_return_early {
586 let used = budget_tokens - state.remaining_budget;
587 return SelectionResult {
588 selected: state.selected,
589 reason: SelectionReason::BudgetExhausted,
590 used_tokens: used,
591 utility: utility_value(&state.utility_state),
592 greedy_iters: 0,
593 stopping_certificate: 0.0,
594 };
595 }
596
597 let base_state = state.utility_state.copy();
598 let base_selected = state.selected.clone();
599 let base_budget = state.remaining_budget;
600
601 let candidates: Vec<Fragment> = non_core_fragments
602 .iter()
603 .filter(|f| !state.selected_ids.overlaps(f))
604 .cloned()
605 .collect();
606 let candidates = drop_redundant_signatures(&candidates, state.remaining_budget);
607
608 let mut id_to_frag: FxHashMap<FragmentId, Fragment> = FxHashMap::default();
609 let mut heap = build_initial_heap(
610 &candidates,
611 rel,
612 needs,
613 &state.utility_state,
614 &mut id_to_frag,
615 );
616
617 let (_, threshold, greedy_iters) = run_greedy_loop_heap(
618 &mut heap,
619 &id_to_frag,
620 &mut state,
621 rel,
622 needs,
623 tau,
624 budget_tokens,
625 );
626
627 let greedy_utility = utility_value(&state.utility_state);
628
629 let mut base_selected_ids = IntervalIndex::new();
630 for f in &base_selected {
631 base_selected_ids.add_id(&f.id);
632 }
633
634 let (best_singleton, best_gain) = find_best_singleton(
635 &non_core_fragments,
636 &base_selected_ids,
637 base_budget,
638 rel,
639 needs,
640 &base_state,
641 );
642
643 let empty_state = init_selection_state(core_ids, rel, budget_tokens, file_importance);
644 let (full_singleton, full_singleton_gain) = find_best_singleton_full_set(
645 &fragments,
646 budget_tokens,
647 rel,
648 needs,
649 &empty_state.utility_state,
650 );
651
652 let mut best_alt_utility = greedy_utility;
653 let mut best_alt: Option<(Vec<Fragment>, u32)> = None;
654
655 if let Some(ref singleton) = best_singleton {
656 let u = utility_value(&base_state) + best_gain;
657 if u > best_alt_utility {
658 best_alt_utility = u;
659 let used = budget_tokens - (base_budget - singleton.token_count);
660 let mut sel = base_selected.clone();
661 sel.push(singleton.clone());
662 best_alt = Some((sel, used));
663 }
664 }
665
666 if let Some(ref full) = full_singleton {
667 let u = utility_value(&empty_state.utility_state) + full_singleton_gain;
668 if u > best_alt_utility {
669 best_alt_utility = u;
670 best_alt = Some((vec![full.clone()], full.token_count));
671 }
672 }
673
674 if let Some((sel, used)) = best_alt {
675 return SelectionResult {
676 selected: sel,
677 reason: SelectionReason::BestSingleton,
678 used_tokens: used,
679 utility: best_alt_utility,
680 greedy_iters,
681 stopping_certificate: 0.0,
682 };
683 }
684
685 let used = budget_tokens - state.remaining_budget;
686 let reason = if state.remaining_budget == 0 {
687 SelectionReason::BudgetExhausted
688 } else if greedy_utility <= 0.0 {
689 SelectionReason::NoUtility
690 } else if state.selected.is_empty() || state.selected.len() == base_selected.len() {
691 SelectionReason::NoCandidates
692 } else if threshold > 0.0 && !heap.is_empty() {
693 SelectionReason::StoppedByTau
694 } else {
695 SelectionReason::NoCandidates
696 };
697
698 let stopping_certificate = if matches!(reason, SelectionReason::StoppedByTau) {
699 threshold * f64::from(state.remaining_budget)
700 } else {
701 0.0
702 };
703
704 SelectionResult {
705 selected: state.selected,
706 reason,
707 used_tokens: used,
708 utility: greedy_utility,
709 greedy_iters,
710 stopping_certificate,
711 }
712}