Skip to content

Commit 89b4255

Browse files
evanlinjinclaude
andcommitted
docs: fix unresolved rustdoc links + rustfmt reflow
Three rustdoc warnings: - `SelectionView` referenced the pub(crate) `crate::bnb::BnbIter` in prose. Reworded to describe the behaviour without naming the internal type. - `Drain` linked to `CoinSelector::drain`, which moved to `SelectionView::drain`. Retargeted. - `metrics` module docs used `[CoinSelector::run_bnb]` / `bnb_solutions` with no `use` in module scope. Switched to fully-qualified `crate::CoinSelector::...` paths. Also picks up rustfmt reflows across the changed files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 4088d20 commit 89b4255

7 files changed

Lines changed: 57 additions & 53 deletions

File tree

src/bnb.rs

Lines changed: 18 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
11
//! Branch-and-bound search.
22
//!
3-
//! BnB explores a binary tree where each [`Branch`] expands into two
4-
//! children: an *inclusion* child that selects the candidate at the
5-
//! branch's `cursor`, and an *exclusion* child that bans it along with
6-
//! any same-(value, weight) duplicates that immediately follow.
3+
//! BnB explores a binary tree where each [`Branch`] expands into two children: an *inclusion*
4+
//! child that selects the candidate at the branch's `cursor`, and an *exclusion* child that bans
5+
//! it along with any same-(value, weight) duplicates that immediately follow.
76
//!
8-
//! Cursors only advance as we descend — inclusion's child has cursor
9-
//! `parent + 1`; exclusion's jumps past the banned duplicates. That
10-
//! invariant lets `insert_new_branches` skip directly to the next
11-
//! undecided candidate without re-scanning `selected` / `banned`. The
12-
//! only scanning happens to step past pre-selected / pre-banned positions
13-
//! (rare, lazy).
14-
7+
//! Cursors only advance as we descend — inclusion's child has cursor `parent + 1`; exclusion's
8+
//! jumps past the banned duplicates. That invariant lets `insert_new_branches` skip directly to the
9+
//! next undecided candidate without re-scanning `selected` / `banned` from the start.
1510
use core::cmp::Reverse;
1611

1712
use crate::{float::Ordf32, Drain, SelectionCache, SelectionView, Target};
@@ -97,7 +92,9 @@ impl<'a, M: BnbMetric> BnbIter<'a, M> {
9792
is_exclusion: bool,
9893
cursor: usize,
9994
) {
100-
let bound = self.metric.bound(&SelectionView::with_cache(cs, cache), self.target);
95+
let bound = self
96+
.metric
97+
.bound(&SelectionView::with_cache(cs, cache), self.target);
10198
if let Some(bound) = bound {
10299
let is_good_enough = match self.best {
103100
Some(best) => best > bound,
@@ -115,16 +112,10 @@ impl<'a, M: BnbMetric> BnbIter<'a, M> {
115112
}
116113
}
117114

118-
fn insert_new_branches(
119-
&mut self,
120-
cs: &CoinSelector<'a>,
121-
cache: &SelectionCache,
122-
start: usize,
123-
) {
124-
// Find the position to expand on: at or after `start`, whichever is
125-
// the first candidate that's neither selected nor banned. Usually
126-
// this *is* `start` — the only reason to advance is to skip past
127-
// pre-selected/pre-banned candidates (see module-level docs).
115+
fn insert_new_branches(&mut self, cs: &CoinSelector<'a>, cache: &SelectionCache, start: usize) {
116+
// Find the position to expand on: at or after `start`, whichever is the first candidate
117+
// that's neither selected nor banned. Usually this *is* `start` — the only reason to
118+
// advance is to skip past pre-selected/pre-banned candidates (see module-level docs).
128119
let mut iter = cs.candidates().skip(start);
129120
let mut cursor = start;
130121
let (here_idx, here_cand) = loop {
@@ -147,10 +138,9 @@ impl<'a, M: BnbMetric> BnbIter<'a, M> {
147138
inclusion_cache.add(here_cand);
148139
self.consider_adding_to_queue(&inclusion_cs, &inclusion_cache, false, cursor + 1);
149140

150-
// Exclusion: descendants explore "this candidate is *not* selected".
151-
// Bans this and every consecutive same-(value, weight) candidate —
152-
// they're equivalent choices, so we deduplicate by handling the
153-
// entire equivalence class in one branch. The cursor jumps past
141+
// Exclusion: descendants explore "this candidate is *not* selected". Bans this and every
142+
// consecutive same-(value, weight) candidate — they're equivalent choices, so we
143+
// deduplicate by handling the entire equivalence class in one branch. The cursor jumps past
154144
// all of them.
155145
let mut exclusion_cs = cs.clone();
156146
exclusion_cs.ban(here_idx);
@@ -173,9 +163,8 @@ struct Branch<'a> {
173163
selector: CoinSelector<'a>,
174164
cache: SelectionCache,
175165
is_exclusion: bool,
176-
/// Position in `candidate_order` of the candidate whose include /
177-
/// exclude decision creates this branch's two children. See the
178-
/// module-level "Cursor invariant" section.
166+
/// Position in `candidate_order` of the candidate whose include/exclude decision creates this
167+
/// branch's two children. See the module-level docs for more.
179168
cursor: usize,
180169
}
181170

src/coin_selector.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,10 @@ impl<'a> CoinSelector<'a> {
244244
pub fn select_until_target_met(&mut self, target: Target) -> Result<(), InsufficientFunds> {
245245
self.select_until(|cs| cs.compute_view().is_target_met(target))
246246
.ok_or_else(|| InsufficientFunds {
247-
missing: self.compute_view().excess(target, Drain::NONE).unsigned_abs(),
247+
missing: self
248+
.compute_view()
249+
.excess(target, Drain::NONE)
250+
.unsigned_abs(),
248251
})
249252
}
250253

src/drain.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,10 @@ impl DrainWeights {
7070
/// A drain (A.K.A. change) output.
7171
/// Technically it could represent multiple outputs.
7272
///
73-
/// This is returned from [`CoinSelector::drain`]. Note if `drain` returns a drain where `is_none()`
74-
/// returns true then **no change should be added** to the transaction.
73+
/// This is returned from [`SelectionView::drain`]. Note if `drain` returns a drain where
74+
/// `is_none()` returns true then **no change should be added** to the transaction.
7575
///
76-
/// [`CoinSelector::drain`]: crate::CoinSelector::drain
76+
/// [`SelectionView::drain`]: crate::SelectionView::drain
7777
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
7878
pub struct Drain {
7979
/// Weight of adding drain output and spending the drain output.

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ mod coin_selector;
1717
pub mod float;
1818
pub use coin_selector::*;
1919
mod selection_view;
20-
pub use selection_view::SelectionView;
2120
use selection_view::SelectionCache;
21+
pub use selection_view::SelectionView;
2222

2323
mod bnb;
2424
pub use bnb::*;

src/selection_view.rs

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,11 @@ impl SelectionCache {
4747
let is_segwit_tx = self.segwit_count > 0;
4848
let witness_header_extra_weight = is_segwit_tx as u64 * 2;
4949
let nonsegwit_count = self.candidate_count - self.segwit_count;
50-
let segwit_adjust = if is_segwit_tx { nonsegwit_count as u64 } else { 0 };
50+
let segwit_adjust = if is_segwit_tx {
51+
nonsegwit_count as u64
52+
} else {
53+
0
54+
};
5155
let input_varint_weight = varint_size(self.input_count) * 4;
5256
input_varint_weight + self.weight_sum + segwit_adjust + witness_header_extra_weight
5357
}
@@ -84,9 +88,9 @@ impl SelectionCache {
8488
/// [`BnbMetric::bound`](crate::BnbMetric::bound) in the BnB hot path.
8589
///
8690
/// Methods on this type read pre-computed aggregates rather than walking the
87-
/// selected bitset, so they are constant-time. The cache is maintained
88-
/// incrementally by [`crate::bnb::BnbIter`], which is what makes the metric
89-
/// evaluator "delta-aware".
91+
/// selected bitset, so they are constant-time. During branch-and-bound
92+
/// search, the cache is maintained incrementally as branches are explored,
93+
/// which is what makes the metric evaluator "delta-aware".
9094
///
9195
/// `SelectionView` implements [`Deref<Target = CoinSelector>`](Deref), so every
9296
/// `&self` method of [`CoinSelector`] is reachable directly on the view.
@@ -108,10 +112,7 @@ impl<'a> Deref for SelectionView<'a> {
108112
impl<'a> SelectionView<'a> {
109113
/// Construct a view that borrows an externally maintained cache. Caller is
110114
/// responsible for keeping the cache in sync with `selector`'s selection.
111-
pub(crate) fn with_cache(
112-
selector: &'a CoinSelector<'a>,
113-
cache: &'a SelectionCache,
114-
) -> Self {
115+
pub(crate) fn with_cache(selector: &'a CoinSelector<'a>, cache: &'a SelectionCache) -> Self {
115116
Self {
116117
selector,
117118
cache: Cow::Borrowed(cache),
@@ -316,7 +317,11 @@ impl<'a> SelectionView<'a> {
316317
/// the larger of the two.
317318
pub fn implied_fee(&self, target: Target, drain_weights: DrainWeights) -> u64 {
318319
let tx_weight = self.weight(target.outputs, drain_weights);
319-
let mut implied_fee = target.fee.rate.implied_fee(tx_weight).max(target.fee.absolute);
320+
let mut implied_fee = target
321+
.fee
322+
.rate
323+
.implied_fee(tx_weight)
324+
.max(target.fee.absolute);
320325
if let Some(replace) = target.fee.replace {
321326
implied_fee = Ord::max(implied_fee, replace.min_fee_to_do_replacement(tx_weight));
322327
}
@@ -328,11 +333,7 @@ impl<'a> SelectionView<'a> {
328333
/// telling you what target feerate you currently have.
329334
///
330335
/// Returns `None` if the feerate would be negative or infinity.
331-
pub fn implied_feerate(
332-
&self,
333-
target_outputs: TargetOutputs,
334-
drain: Drain,
335-
) -> Option<FeeRate> {
336+
pub fn implied_feerate(&self, target_outputs: TargetOutputs, drain: Drain) -> Option<FeeRate> {
336337
let numerator =
337338
self.selected_value() as i64 - target_outputs.value_sum as i64 - drain.value as i64;
338339
let denom = self.weight(target_outputs, drain.weights);

tests/bnb.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,12 @@ fn bnb_finds_an_exact_solution_in_n_iter() {
106106
assert_eq!(rounds, 3194);
107107
let best_view = best.compute_view();
108108
assert_eq!(best_view.input_weight(), solution_weight);
109-
assert_eq!(best_view.selected_value(), target_value, "score={:?}", score);
109+
assert_eq!(
110+
best_view.selected_value(),
111+
target_value,
112+
"score={:?}",
113+
score
114+
);
110115
}
111116

112117
#[test]

tests/common.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,12 @@ where
171171
.flatten()
172172
.filter(|(_, inc)| *inc)
173173
{
174-
if let Some(descendant_score) = metric.score(&descendant_cs.compute_view(), target) {
174+
if let Some(descendant_score) = metric.score(&descendant_cs.compute_view(), target)
175+
{
175176
let parent_has_change = metric.drain(&cs.compute_view(), target).is_some();
176-
let descendant_has_change =
177-
metric.drain(&descendant_cs.compute_view(), target).is_some();
177+
let descendant_has_change = metric
178+
.drain(&descendant_cs.compute_view(), target)
179+
.is_some();
178180
prop_assert!(
179181
descendant_score >= lb_score,
180182
"
@@ -351,7 +353,11 @@ where
351353
.enumerate()
352354
.inspect(|(i, _)| rounds = *i)
353355
.filter(|(_, (_, inclusion))| *inclusion)
354-
.filter_map(|(_, (cs, _))| metric.score(&cs.compute_view(), target).map(|score| (cs, score)));
356+
.filter_map(|(_, (cs, _))| {
357+
metric
358+
.score(&cs.compute_view(), target)
359+
.map(|score| (cs, score))
360+
});
355361

356362
for (child_cs, score) in iter {
357363
match &mut best {

0 commit comments

Comments
 (0)