Skip to main content

_diffctx/utility/
boltzmann.rs

1use rustc_hash::{FxHashMap, FxHashSet};
2
3use crate::config::selection::boltzmann;
4use crate::select::{SelectionReason, SelectionResult};
5use crate::types::{Fragment, FragmentId};
6
7fn boltzmann_weight(rel_score: f64, token_count: u32, beta: f64) -> f64 {
8    rel_score * (-beta * token_count as f64).exp()
9}
10
11fn nonoverlapping(selected: &[Fragment], frag: &Fragment) -> bool {
12    selected.iter().all(|s| {
13        if s.path() != frag.path() {
14            return true;
15        }
16        frag.end_line() < s.start_line() || frag.start_line() > s.end_line()
17    })
18}
19
20pub fn boltzmann_select(
21    fragments: &[Fragment],
22    core_ids: &FxHashSet<FragmentId>,
23    rel: &FxHashMap<FragmentId, f64>,
24    budget_tokens: u32,
25    beta: f64,
26) -> SelectionResult {
27    if fragments.is_empty() {
28        return SelectionResult {
29            selected: Vec::new(),
30            reason: SelectionReason::NoCandidates,
31            used_tokens: 0,
32            utility: 0.0,
33            greedy_iters: 0,
34            stopping_certificate: 0.0,
35        };
36    }
37
38    let mut core: Vec<Fragment> = fragments
39        .iter()
40        .filter(|f| core_ids.contains(&f.id))
41        .cloned()
42        .collect();
43    core.sort_by_key(|f| f.token_count);
44
45    let mut selected: Vec<Fragment> = Vec::new();
46    let mut used: u32 = 0;
47    for f in core {
48        if used + f.token_count > budget_tokens {
49            continue;
50        }
51        if !nonoverlapping(&selected, &f) {
52            continue;
53        }
54        used += f.token_count;
55        selected.push(f);
56    }
57
58    let mut ranked: Vec<(f64, Fragment)> = fragments
59        .iter()
60        .filter(|f| !core_ids.contains(&f.id) && f.token_count > 0)
61        .map(|f| {
62            let r = rel.get(&f.id).copied().unwrap_or(0.0);
63            (boltzmann_weight(r, f.token_count, beta), f.clone())
64        })
65        .filter(|(w, _)| *w > 0.0)
66        .collect();
67    ranked.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
68
69    let mut total_utility = 0.0;
70    let mut reason = SelectionReason::TopK;
71    for (w, f) in ranked {
72        if used + f.token_count > budget_tokens {
73            reason = SelectionReason::BudgetExhausted;
74            continue;
75        }
76        if !nonoverlapping(&selected, &f) {
77            continue;
78        }
79        used += f.token_count;
80        total_utility += w;
81        selected.push(f);
82    }
83
84    let final_count = selected.len();
85    SelectionResult {
86        selected,
87        reason,
88        used_tokens: used,
89        utility: total_utility,
90        greedy_iters: final_count,
91        stopping_certificate: 0.0,
92    }
93}
94
95pub fn calibrate_beta(
96    fragments: &[Fragment],
97    core_ids: &FxHashSet<FragmentId>,
98    rel: &FxHashMap<FragmentId, f64>,
99    budget_tokens: u32,
100    epsilon: f64,
101) -> f64 {
102    let cfg = boltzmann();
103    let (mut lo, mut hi) = (cfg.beta_lo, cfg.beta_hi);
104    let target = budget_tokens as f64;
105    let tol = (target * epsilon).max(1.0);
106
107    // `boltzmann_select` budget-caps `used_tokens` at `target`, so the cost
108    // function is monotone non-increasing in β and saturates at `target` for
109    // small β. The well-defined calibration target is therefore "the largest
110    // β whose selection still saturates the budget" — symmetric `|cost−B|<ε`
111    // collapses to one-sided `cost ≥ B−tol` under this saturation. Both
112    // bisection branches stay live: `lo=mid` advances when cost still
113    // saturates (try larger β), `hi=mid` retreats when cost has dropped
114    // (try smaller β). Loop invariant: `cost(lo) ≥ target−tol`.
115
116    let cost_lo = boltzmann_select(fragments, core_ids, rel, budget_tokens, lo).used_tokens as f64;
117    if cost_lo < target - tol {
118        return lo;
119    }
120    let cost_hi = boltzmann_select(fragments, core_ids, rel, budget_tokens, hi).used_tokens as f64;
121    if cost_hi >= target - tol {
122        return hi;
123    }
124
125    for _ in 0..cfg.bisect_iters {
126        let mid = (lo * hi).sqrt();
127        let cost =
128            boltzmann_select(fragments, core_ids, rel, budget_tokens, mid).used_tokens as f64;
129        if cost >= target - tol {
130            lo = mid;
131        } else {
132            hi = mid;
133        }
134    }
135    lo
136}
137
138#[cfg(test)]
139mod paper_claim_tests {
140    //! Empirical validation of Boltzmann-related paper claims.
141    //!
142    //!  - Claim 8 (paper §4.5): selected cost is monotonically non-increasing in β
143    //!    (β₁ < β₂ ⇒ E[c(C*(β₂))] ≤ E[c(C*(β₁))]).
144    //!    Without monotonicity the bisection in `calibrate_beta` does not converge.
145
146    use super::*;
147    use crate::types::{FragmentId, FragmentKind};
148    use std::sync::Arc;
149
150    fn frag(name: &str, tokens: u32) -> Fragment {
151        Fragment {
152            id: FragmentId::new(Arc::from(format!("syn/{name}.rs")), 1, 10),
153            kind: FragmentKind::Function,
154            content: Arc::from(""),
155            identifiers: FxHashSet::default(),
156            token_count: tokens,
157            symbol_name: Some(name.to_lowercase()),
158        }
159    }
160
161    /// Tilde-utility under Boltzmann reweighting (paper §3, before greedy):
162    /// $\tilde{U}(C) = \sum_{f \in C} w(f) \cdot e^{-\beta |f|}$.
163    /// This must be modular — pure sum decomposition with no cross-term.
164    fn tilde_utility(set: &[usize], fragments: &[Fragment], rels: &[f64], beta: f64) -> f64 {
165        set.iter()
166            .map(|&i| rels[i] * (-beta * fragments[i].token_count as f64).exp())
167            .sum()
168    }
169
170    fn xorshift(state: &mut u64) -> u64 {
171        *state ^= *state << 13;
172        *state ^= *state >> 7;
173        *state ^= *state << 17;
174        *state
175    }
176
177    fn random_subset(n: usize, fraction: f64, rng: &mut u64) -> Vec<usize> {
178        (0..n)
179            .filter(|_| (xorshift(rng) % 1000) as f64 / 1000.0 < fraction)
180            .collect()
181    }
182
183    #[test]
184    fn claim_3a_boltzmann_reweighted_utility_is_modular() {
185        let fragments: Vec<Fragment> = (0..10)
186            .map(|i| frag(&format!("f_{i}"), 80 + i * 40))
187            .collect();
188        let rels: Vec<f64> = (0..10).map(|i| 0.2 + 0.07 * i as f64).collect();
189
190        let mut rng = 0xBADCAFE_u64;
191        for &beta in &[1e-4_f64, 1e-2, 0.1, 1.0, 10.0] {
192            for _ in 0..1000 {
193                let a = random_subset(10, 0.5, &mut rng);
194                let b = random_subset(10, 0.5, &mut rng);
195                let union: Vec<usize> = {
196                    let s: FxHashSet<usize> = a.iter().chain(b.iter()).copied().collect();
197                    let mut v: Vec<usize> = s.into_iter().collect();
198                    v.sort();
199                    v
200                };
201                let intersection: Vec<usize> = {
202                    let sa: FxHashSet<usize> = a.iter().copied().collect();
203                    let mut v: Vec<usize> = b.iter().copied().filter(|i| sa.contains(i)).collect();
204                    v.sort();
205                    v
206                };
207                let lhs = tilde_utility(&union, &fragments, &rels, beta);
208                let rhs = tilde_utility(&a, &fragments, &rels, beta)
209                    + tilde_utility(&b, &fragments, &rels, beta)
210                    - tilde_utility(&intersection, &fragments, &rels, beta);
211                assert!(
212                    (lhs - rhs).abs() < 1e-9,
213                    "Boltzmann tilde-utility not modular at β={beta}: \
214                     lhs={lhs}, rhs={rhs}, |Δ|={}",
215                    (lhs - rhs).abs()
216                );
217            }
218        }
219    }
220
221    #[test]
222    fn claim_8_boltzmann_cost_is_monotone_in_beta() {
223        let fragments: Vec<Fragment> = (0..12)
224            .map(|i| frag(&format!("f_{i}"), 50 + i * 60))
225            .collect();
226        let mut rels: FxHashMap<FragmentId, f64> = FxHashMap::default();
227        for (i, f) in fragments.iter().enumerate() {
228            rels.insert(f.id.clone(), 0.2 + 0.05 * i as f64);
229        }
230        let core_ids = FxHashSet::default();
231        let budget: u32 = 4096;
232
233        let betas = [1e-6, 1e-4, 1e-2, 1.0, 100.0];
234        let costs: Vec<u32> = betas
235            .iter()
236            .map(|&b| boltzmann_select(&fragments, &core_ids, &rels, budget, b).used_tokens)
237            .collect();
238
239        for i in 0..costs.len() - 1 {
240            assert!(
241                costs[i] >= costs[i + 1],
242                "Boltzmann cost not monotone in β: at β={}→{} cost {}→{} (sequence: {costs:?})",
243                betas[i],
244                betas[i + 1],
245                costs[i],
246                costs[i + 1]
247            );
248        }
249
250        assert!(
251            costs[0] > costs[costs.len() - 1],
252            "β sweep produced no cost variation; sweep range may be too narrow (costs: {costs:?})"
253        );
254    }
255}