1use crate::{
2 types::{
3 CoinSelectionOpt, EffectiveValue, OutputGroup, SelectionError, SelectionOutput,
4 WasteMetric, Weight,
5 },
6 utils::{calculate_accumulated_weight, calculate_fee, calculate_waste, effective_value},
7};
8use rand::{thread_rng, Rng};
9use std::{cmp::Reverse, collections::HashSet};
10
11pub fn select_coin_knapsack(
12 inputs: &[OutputGroup],
13 options: &CoinSelectionOpt,
14) -> Result<SelectionOutput, SelectionError> {
15 let adjusted_target = options.target_value
16 + options.min_change_value
17 + calculate_fee(options.base_weight, options.target_feerate).unwrap_or_default();
18 let mut smaller_coins = inputs
19 .iter()
20 .enumerate()
21 .filter(|&(_, output_group)| output_group.value < adjusted_target)
22 .map(|(index, output_group)| {
23 (
24 index,
25 effective_value(output_group, options.target_feerate),
26 output_group.weight,
27 )
28 })
29 .collect::<Vec<_>>();
30 smaller_coins.sort_by_key(|(_, value, _)| Reverse(*value));
31 let smaller_coins: Vec<_> = smaller_coins
32 .into_iter()
33 .filter_map(|(index, value, weight)| value.ok().map(|v| (index, v, weight)))
34 .collect();
35 knap_sack(adjusted_target, &smaller_coins, options)
36}
37
38fn knap_sack(
39 adjusted_target: u64,
40 smaller_coins: &[(usize, EffectiveValue, Weight)],
41 options: &CoinSelectionOpt,
42) -> Result<SelectionOutput, SelectionError> {
43 let mut selected_inputs: HashSet<usize> = HashSet::new();
44 let mut accumulated_value: u64 = 0;
45 let mut best_set: HashSet<usize> = HashSet::new();
46 let mut best_set_value: u64 = u64::MAX;
47 let mut rng = thread_rng();
48 for _ in 1..=1000 {
49 for pass in 1..=2 {
50 for &(index, value, _) in smaller_coins {
51 let toss_result: bool = rng.gen_bool(0.5);
52 if (pass == 2 && !selected_inputs.contains(&index)) || (pass == 1 && toss_result) {
53 selected_inputs.insert(index);
54 accumulated_value += value;
55 if accumulated_value == adjusted_target {
56 let accumulated_weight =
57 calculate_accumulated_weight(smaller_coins, &selected_inputs);
58 let estimated_fees =
59 calculate_fee(accumulated_weight, options.target_feerate);
60 let index_vector: Vec<usize> = selected_inputs.into_iter().collect();
61 let waste: u64 = calculate_waste(
62 options,
63 accumulated_value,
64 accumulated_weight,
65 estimated_fees?,
66 );
67 return Ok(SelectionOutput {
68 selected_inputs: index_vector,
69 waste: WasteMetric(waste),
70 });
71 } else if accumulated_value >= adjusted_target {
72 if accumulated_value < best_set_value {
73 best_set_value = accumulated_value;
74 best_set.clone_from(&selected_inputs);
75 }
76 selected_inputs.remove(&index);
77 accumulated_value -= value;
78 }
79 }
80 }
81 }
82 accumulated_value = 0;
83 selected_inputs.clear();
84 }
85 if best_set_value == u64::MAX {
86 Err(SelectionError::NoSolutionFound)
87 } else {
88 let best_set_weight = calculate_accumulated_weight(smaller_coins, &best_set);
89 let estimated_fees = calculate_fee(best_set_weight, options.target_feerate);
90 let index_vector: Vec<usize> = best_set.into_iter().collect();
91 let waste: u64 = calculate_waste(options, best_set_value, best_set_weight, estimated_fees?);
92 Ok(SelectionOutput {
93 selected_inputs: index_vector,
94 waste: WasteMetric(waste),
95 })
96 }
97}
98
99#[cfg(test)]
100mod test {
101
102 use crate::{
103 algorithms::knapsack::select_coin_knapsack,
104 types::{CoinSelectionOpt, ExcessStrategy, OutputGroup, SelectionError},
105 utils::calculate_fee,
106 };
107
108 const CENT: f64 = 1000000.0;
109 const COIN: f64 = 100000000.0;
110 const RUN_TESTS: u32 = 100;
111 const RUN_TESTS_SLIM: u32 = 10;
112
113 fn knapsack_setup_options(adjusted_target: u64, target_feerate: f32) -> CoinSelectionOpt {
114 let min_change_value = 500;
115 let base_weight = 10;
116 let target_value = adjusted_target
117 - min_change_value
118 - calculate_fee(base_weight, target_feerate).unwrap_or_default();
119 CoinSelectionOpt {
120 target_value,
121 target_feerate, long_term_feerate: Some(0.4),
123 min_absolute_fee: 0,
124 base_weight,
125 change_weight: 50,
126 change_cost: 10,
127 avg_input_weight: 20,
128 avg_output_weight: 10,
129 min_change_value,
130 excess_strategy: ExcessStrategy::ToChange,
131 }
132 }
133
134 fn knapsack_setup_output_groups(
135 value: Vec<u64>,
136 weights: Vec<u64>,
137 target_feerate: f32,
138 ) -> Vec<OutputGroup> {
139 let mut inputs: Vec<OutputGroup> = Vec::new();
140 for (i, j) in value.into_iter().zip(weights.into_iter()) {
141 let k = i.saturating_add(calculate_fee(j, target_feerate).unwrap_or_default());
144 inputs.push(OutputGroup {
145 value: k,
146 weight: j,
147 input_count: 1,
148 creation_sequence: None,
149 })
150 }
151 inputs
152 }
153
154 fn knapsack_add_to_output_group(
155 inputs: &mut Vec<OutputGroup>,
156 value: Vec<u64>,
157 weights: Vec<u64>,
158 target_feerate: f32,
159 ) {
160 for (i, j) in value.into_iter().zip(weights.into_iter()) {
161 let k = i.saturating_add(calculate_fee(j, target_feerate).unwrap_or_default());
164 inputs.push(OutputGroup {
165 value: k,
166 weight: j,
167 input_count: 1,
168 creation_sequence: None,
169 })
170 }
171 }
172
173 fn knapsack_test_vectors() {
174 let mut inputs_verify: Vec<usize> = Vec::new();
175 for _ in 0..RUN_TESTS {
176 let mut inputs: Vec<OutputGroup> = Vec::new();
178 let mut options = knapsack_setup_options(1000, 0.33);
179 let mut result = select_coin_knapsack(&inputs, &options);
180 assert!(matches!(result, Err(SelectionError::NoSolutionFound)));
181
182 inputs = knapsack_setup_output_groups(
184 vec![(2.0 * CENT).round() as u64, (1.0 * CENT).round() as u64],
185 vec![130, 100],
186 0.56,
187 );
188 options = knapsack_setup_options((3.0 * CENT).round() as u64, 0.56);
189 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
190 assert_eq!(result.selected_inputs.len(), 2);
192 inputs_verify = vec![0, 1];
194 assert!(inputs_verify
195 .iter()
196 .all(|&item| result.selected_inputs.contains(&item)));
197 }
198 inputs_verify.clear();
199 knapsack_add_to_output_group(
201 &mut inputs,
202 vec![
203 (5.0 * CENT).round() as u64,
204 (10.0 * CENT).round() as u64,
205 (20.0 * CENT).round() as u64,
206 ],
207 vec![100, 10, 50],
208 0.56,
209 );
210 options = knapsack_setup_options((37.0 * CENT).round() as u64, 0.56);
212 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
213 assert_eq!(result.selected_inputs.len(), 4);
215 inputs_verify = vec![4, 3, 2, 0];
217 assert!(inputs_verify
218 .iter()
219 .all(|&item| result.selected_inputs.contains(&item)));
220 }
221 inputs_verify.clear();
222 options = knapsack_setup_options((38.0 * CENT).round() as u64, 0.56);
224 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
225 assert_eq!(result.selected_inputs.len(), 5);
227 inputs_verify = vec![4, 3, 2, 1, 0];
229 assert!(inputs_verify
230 .iter()
231 .all(|&item| result.selected_inputs.contains(&item)));
232 }
233 inputs_verify.clear();
234 options = knapsack_setup_options((34.0 * CENT).round() as u64, 0.56);
236 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
237 assert_eq!(result.selected_inputs.len(), 3);
239 inputs_verify = vec![4, 3, 2];
241 assert!(inputs_verify
242 .iter()
243 .all(|&item| result.selected_inputs.contains(&item)));
244 }
245 inputs_verify.clear();
246 options = knapsack_setup_options((7.0 * CENT).round() as u64, 0.56);
248 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
249 assert_eq!(result.selected_inputs.len(), 2);
251 inputs_verify = vec![0, 2];
253 assert!(inputs_verify
254 .iter()
255 .all(|&item| result.selected_inputs.contains(&item)));
256 }
257 inputs_verify.clear();
258 options = knapsack_setup_options((8.0 * CENT).round() as u64, 0.56);
260 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
261 assert_eq!(result.selected_inputs.len(), 3);
263 inputs_verify = vec![0, 2, 1];
265 assert!(inputs_verify
266 .iter()
267 .all(|&item| result.selected_inputs.contains(&item)));
268 }
269 inputs_verify.clear();
270 options = knapsack_setup_options((10.0 * CENT).round() as u64, 0.56);
272 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
273 assert_eq!(result.selected_inputs.len(), 1);
275 inputs_verify = vec![10];
277 assert!(inputs_verify
278 .iter()
279 .all(|&item| result.selected_inputs.contains(&item)));
280 }
281 inputs_verify.clear();
282 inputs.clear();
284 inputs = knapsack_setup_output_groups(
287 vec![
288 (6.0 * CENT).round() as u64,
289 (7.0 * CENT).round() as u64,
290 (8.0 * CENT).round() as u64,
291 (20.0 * CENT).round() as u64,
292 (30.0 * CENT).round() as u64,
293 ],
294 vec![100, 200, 100, 10, 5],
295 0.77,
296 );
297 options = knapsack_setup_options((72.0 * CENT).round() as u64, 0.77);
299 result = select_coin_knapsack(&inputs, &options);
300 assert!(matches!(result, Err(SelectionError::NoSolutionFound)));
301 options = knapsack_setup_options((16.0 * CENT).round() as u64, 0.77);
303 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
304 assert_eq!(result.selected_inputs.len(), 3);
306 inputs_verify = vec![0, 1, 2];
308 assert!(inputs_verify
309 .iter()
310 .all(|&item| result.selected_inputs.contains(&item)));
311 }
312 inputs_verify.clear();
313 knapsack_add_to_output_group(
315 &mut inputs,
316 vec![(5.0 * CENT).round() as u64],
317 vec![10],
318 0.77,
319 );
320 options = knapsack_setup_options((16.0 * CENT).round() as u64, 0.77);
322 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
323 assert_eq!(result.selected_inputs.len(), 3);
325 inputs_verify = vec![0, 1, 5];
327 assert!(inputs_verify
328 .iter()
329 .all(|&item| result.selected_inputs.contains(&item)));
330 }
331 inputs_verify.clear();
332
333 knapsack_add_to_output_group(
335 &mut inputs,
336 vec![(18.0 * CENT).round() as u64],
337 vec![1],
338 0.77,
339 );
340 options = knapsack_setup_options((11.0 * CENT).round() as u64, 0.77);
342 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
343 assert_eq!(result.selected_inputs.len(), 2);
345 inputs_verify = vec![0, 5];
347 assert!(inputs_verify
348 .iter()
349 .all(|&item| result.selected_inputs.contains(&item)));
350 }
351 inputs_verify.clear();
352 inputs.clear();
354 inputs = knapsack_setup_output_groups(
356 vec![
357 (0.101 * CENT).round() as u64,
358 (0.201 * CENT).round() as u64,
359 (0.301 * CENT).round() as u64,
360 (0.401 * CENT).round() as u64,
361 (0.501 * CENT).round() as u64,
362 ],
363 vec![14, 45, 6, 10, 100],
364 0.56,
365 );
366 options = knapsack_setup_options((1.0 * CENT).round() as u64, 0.56);
368 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
369 assert_eq!(result.selected_inputs.len(), 3);
371 inputs_verify = vec![0, 3, 4];
373 let valid_inputs_1 = inputs_verify
374 .iter()
375 .all(|&item| result.selected_inputs.contains(&item));
376 inputs_verify.clear();
377 inputs_verify = vec![1, 2, 4];
378 let valid_inputs_2 = inputs_verify
379 .iter()
380 .all(|&item| result.selected_inputs.contains(&item));
381 assert!(valid_inputs_1 || valid_inputs_2);
382 }
383 inputs_verify.clear();
384 inputs.clear();
386 inputs = knapsack_setup_output_groups(
388 vec![
389 (50000.0 * COIN).round() as u64,
390 (50000.0 * COIN).round() as u64,
391 (50000.0 * COIN).round() as u64,
392 (50000.0 * COIN).round() as u64,
393 (50000.0 * COIN).round() as u64,
394 (50000.0 * COIN).round() as u64,
395 (50000.0 * COIN).round() as u64,
396 (50000.0 * COIN).round() as u64,
397 (50000.0 * COIN).round() as u64,
398 (50000.0 * COIN).round() as u64,
399 (50000.0 * COIN).round() as u64,
400 ],
401 vec![1, 20, 3, 200, 150, 5, 88, 93, 101, 34, 17],
402 0.59,
403 );
404 options = knapsack_setup_options((500000.0 * COIN).round() as u64, 0.59);
406 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
407 assert_eq!(result.selected_inputs.len(), 10);
409 }
410 inputs.clear();
412 inputs = knapsack_setup_output_groups(
414 vec![
415 (0.4 * CENT).round() as u64,
416 (0.6 * CENT).round() as u64,
417 (0.8 * CENT).round() as u64,
418 (1111.0 * CENT).round() as u64,
419 ],
420 vec![14, 45, 6, 10],
421 0.56,
422 );
423 options = knapsack_setup_options((1.0 * CENT).round() as u64, 0.56);
425 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
426 assert_eq!(result.selected_inputs.len(), 2);
428 inputs_verify = vec![0, 1];
430 assert!(inputs_verify
431 .iter()
432 .all(|&item| result.selected_inputs.contains(&item)));
433 }
434 inputs_verify.clear();
435 inputs.clear();
437 inputs = knapsack_setup_output_groups(
439 vec![
440 (100.0 * CENT).round() as u64,
441 (1.0 * CENT).round() as u64,
442 (0.05 * CENT).round() as u64,
443 ],
444 vec![14, 45, 6],
445 0.56,
446 );
447 options = CoinSelectionOpt {
449 target_value: (100.01 * CENT).round() as u64,
450 target_feerate: 0.56, long_term_feerate: Some(0.4),
452 min_absolute_fee: 0,
453 base_weight: 10,
454 change_weight: 50,
455 change_cost: 10,
456 avg_input_weight: 20,
457 avg_output_weight: 10,
458 min_change_value: (0.05 * CENT).round() as u64, excess_strategy: ExcessStrategy::ToChange,
460 };
461 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
462 assert_eq!(result.selected_inputs.len(), 2);
464 inputs_verify = vec![0, 1];
466 assert!(inputs_verify
467 .iter()
468 .all(|&item| result.selected_inputs.contains(&item)));
469 }
470 inputs_verify.clear();
471 inputs.clear();
473 }
474 let mut inputs: Vec<OutputGroup> = Vec::new();
476 let mut amt = 1500;
477 while amt < COIN as u64 {
479 inputs.clear();
480 let mut input_value: Vec<u64> = Vec::new();
482 let mut input_weight: Vec<u64> = Vec::new();
483 for _ in 0..676 {
484 input_value.push(amt);
487 input_weight.push(23);
488 }
489 let inputs = knapsack_setup_output_groups(input_value, input_weight, 0.34);
490 let options = knapsack_setup_options(2000, 0.34);
492 for _ in 0..RUN_TESTS_SLIM {
494 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
495 if let Some(amt_in_inputs) = inputs.first() {
496 if amt_in_inputs.value.checked_sub(2000) < Some(CENT as u64) {
499 let return_size = ((2000.0) / amt as f64).ceil();
501 assert_eq!(result.selected_inputs.len(), return_size as usize);
502 } else {
503 assert_eq!(result.selected_inputs.len(), 1);
505 }
506 } else {
507 println!("unable to access 0th element of input vector");
508 }
509 }
510 }
511 amt *= 10;
512 }
513 inputs.clear();
514 let mut input_value: Vec<u64> = Vec::new();
517 let mut input_weight: Vec<u64> = Vec::new();
518 for _ in 0..=100 {
519 input_value.push(COIN as u64);
521 input_weight.push(23);
522 }
523 let mut inputs = knapsack_setup_output_groups(input_value, input_weight, 0.34);
525 let options = knapsack_setup_options((50.0 * COIN).round() as u64, 0.34);
527 let mut selected_input_1: Vec<usize> = Vec::new();
528 let mut selected_input_2: Vec<usize> = Vec::new();
529 for _ in 0..RUN_TESTS {
530 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
531 selected_input_1.clone_from(&result.selected_inputs);
532 }
533 if let Ok(result) = select_coin_knapsack(&inputs, &options) {
534 selected_input_2.clone_from(&result.selected_inputs);
535 }
536 assert_ne!(selected_input_1, selected_input_2);
538 }
539 selected_input_1.clear();
540 selected_input_2.clear();
541 knapsack_add_to_output_group(
543 &mut inputs,
544 vec![
545 (5.0 * CENT).round() as u64,
546 (10.0 * CENT).round() as u64,
547 (15.0 * CENT).round() as u64,
548 (20.0 * CENT).round() as u64,
549 (25.0 * CENT).round() as u64,
550 ],
551 vec![100, 10, 50, 52, 13],
552 0.34,
553 );
554 }
555
556 #[test]
557 fn test_knapsack() {
558 knapsack_test_vectors();
559 }
560}