Skip to content

Commit 85afe25

Browse files
committed
Split success_probability calculation into two separate methods
In the next commit we'll want to return floats or ints from `success_probability` depending on the callsite, so instead of duplicating the calculation logic, here we split the linear (which always uses int math) and nonlinear (which always uses float math) into separate methods, allowing us to write trivial `success_probability` wrappers that return the desired type.
1 parent cd33ade commit 85afe25

File tree

1 file changed

+82
-46
lines changed

1 file changed

+82
-46
lines changed

lightning/src/routing/scoring.rs

+82-46
Original file line numberDiff line numberDiff line change
@@ -1154,6 +1154,71 @@ fn three_f64_pow_9(a: f64, b: f64, c: f64) -> (f64, f64, f64) {
11541154
(a * a4 * a4, b * b4 * b4, c * c4 * c4)
11551155
}
11561156

1157+
/// If we have no knowledge of the channel, we scale probability down by a multiple of ~82% for the
1158+
/// historical model by multiplying the denominator of a success probability by this before
1159+
/// dividing by 64.
1160+
///
1161+
/// This number (as well as the PDF) was picked experimentally on probing results to maximize the
1162+
/// log-loss of succeeding and failing hops.
1163+
///
1164+
/// Note that we prefer to increase the denominator rather than decrease the numerator as the
1165+
/// denominator is more likely to be larger and thus provide greater precision. This is mostly an
1166+
/// overoptimization but makes a large difference in tests.
1167+
const MIN_ZERO_IMPLIES_NO_SUCCESSES_PENALTY_ON_64: u64 = 78;
1168+
1169+
#[inline(always)]
1170+
fn linear_success_probability(
1171+
total_inflight_amount_msat: u64, min_liquidity_msat: u64, max_liquidity_msat: u64,
1172+
min_zero_implies_no_successes: bool,
1173+
) -> (u64, u64) {
1174+
let (numerator, mut denominator) =
1175+
(max_liquidity_msat - total_inflight_amount_msat,
1176+
(max_liquidity_msat - min_liquidity_msat).saturating_add(1));
1177+
1178+
if min_zero_implies_no_successes && min_liquidity_msat == 0 &&
1179+
denominator < u64::max_value() / MIN_ZERO_IMPLIES_NO_SUCCESSES_PENALTY_ON_64
1180+
{
1181+
denominator = denominator * MIN_ZERO_IMPLIES_NO_SUCCESSES_PENALTY_ON_64 / 64
1182+
}
1183+
1184+
(numerator, denominator)
1185+
}
1186+
1187+
/// Returns a (numerator, denominator) pair each between 0 and 0.0078125, inclusive.
1188+
#[inline(always)]
1189+
fn nonlinear_success_probability(
1190+
total_inflight_amount_msat: u64, min_liquidity_msat: u64, max_liquidity_msat: u64,
1191+
capacity_msat: u64, min_zero_implies_no_successes: bool,
1192+
) -> (f64, f64) {
1193+
let capacity = capacity_msat as f64;
1194+
let max = (max_liquidity_msat as f64) / capacity;
1195+
let min = (min_liquidity_msat as f64) / capacity;
1196+
let amount = (total_inflight_amount_msat as f64) / capacity;
1197+
1198+
// Assume the channel has a probability density function of
1199+
// `128 * (1/256 + 9*(x - 0.5)^8)` for values from 0 to 1 (where 1 is the channel's
1200+
// full capacity). The success probability given some liquidity bounds is thus the
1201+
// integral under the curve from the amount to maximum estimated liquidity, divided by
1202+
// the same integral from the minimum to the maximum estimated liquidity bounds.
1203+
//
1204+
// Because the integral from x to y is simply
1205+
// `128*(1/256 * (y - 0.5) + (y - 0.5)^9) - 128*(1/256 * (x - 0.5) + (x - 0.5)^9), we
1206+
// can calculate the cumulative density function between the min/max bounds trivially.
1207+
// Note that we don't bother to normalize the CDF to total to 1 (using the 128
1208+
// multiple), as it will come out in the division of num / den.
1209+
let (max_norm, min_norm, amt_norm) = (max - 0.5, min - 0.5, amount - 0.5);
1210+
let (max_pow, min_pow, amt_pow) = three_f64_pow_9(max_norm, min_norm, amt_norm);
1211+
let (max_v, min_v, amt_v) = (max_pow + max_norm / 256.0, min_pow + min_norm / 256.0, amt_pow + amt_norm / 256.0);
1212+
let mut denominator = max_v - min_v;
1213+
let numerator = max_v - amt_v;
1214+
1215+
if min_zero_implies_no_successes && min_liquidity_msat == 0 {
1216+
denominator = denominator * (MIN_ZERO_IMPLIES_NO_SUCCESSES_PENALTY_ON_64 as f64) / 64.0;
1217+
}
1218+
1219+
(numerator, denominator)
1220+
}
1221+
11571222
/// Given liquidity bounds, calculates the success probability (in the form of a numerator and
11581223
/// denominator) of an HTLC. This is a key assumption in our scoring models.
11591224
///
@@ -1174,54 +1239,25 @@ fn success_probability(
11741239
debug_assert!(total_inflight_amount_msat < max_liquidity_msat);
11751240
debug_assert!(max_liquidity_msat <= capacity_msat);
11761241

1177-
let (numerator, mut denominator) =
1178-
if params.linear_success_probability {
1179-
(max_liquidity_msat - total_inflight_amount_msat,
1180-
(max_liquidity_msat - min_liquidity_msat).saturating_add(1))
1181-
} else {
1182-
let capacity = capacity_msat as f64;
1183-
let min = (min_liquidity_msat as f64) / capacity;
1184-
let max = (max_liquidity_msat as f64) / capacity;
1185-
let amount = (total_inflight_amount_msat as f64) / capacity;
1186-
1187-
// Assume the channel has a probability density function of
1188-
// `128 * (1/256 + 9*(x - 0.5)^8)` for values from 0 to 1 (where 1 is the channel's
1189-
// full capacity). The success probability given some liquidity bounds is thus the
1190-
// integral under the curve from the amount to maximum estimated liquidity, divided by
1191-
// the same integral from the minimum to the maximum estimated liquidity bounds.
1192-
//
1193-
// Because the integral from x to y is simply
1194-
// `128*(1/256 * (y - 0.5) + (y - 0.5)^9) - 128*(1/256 * (x - 0.5) + (x - 0.5)^9), we
1195-
// can calculate the cumulative density function between the min/max bounds trivially.
1196-
// Note that we don't bother to normalize the CDF to total to 1 (using the 128
1197-
// multiple), as it will come out in the division of num / den.
1198-
let (max_norm, amt_norm, min_norm) = (max - 0.5, amount - 0.5, min - 0.5);
1199-
let (max_pow, amt_pow, min_pow) = three_f64_pow_9(max_norm, amt_norm, min_norm);
1200-
let (max_v, amt_v, min_v) = (max_pow + max_norm / 256.0, amt_pow + amt_norm / 256.0, min_pow + min_norm / 256.0);
1201-
let num = max_v - amt_v;
1202-
let den = max_v - min_v;
1203-
1204-
// Because our numerator and denominator max out at 0.0078125 we need to multiply them
1205-
// by quite a large factor to get something useful (ideally in the 2^30 range).
1206-
const BILLIONISH: f64 = 1024.0 * 1024.0 * 1024.0 * 64.0;
1207-
let numerator = (num * BILLIONISH) as u64 + 1;
1208-
let denominator = (den * BILLIONISH) as u64 + 1;
1209-
debug_assert!(numerator <= 1 << 30, "Got large numerator ({}) from float {}.", numerator, num);
1210-
debug_assert!(denominator <= 1 << 30, "Got large denominator ({}) from float {}.", denominator, den);
1211-
(numerator, denominator)
1212-
};
1242+
if params.linear_success_probability {
1243+
linear_success_probability(total_inflight_amount_msat, min_liquidity_msat, max_liquidity_msat, min_zero_implies_no_successes)
1244+
} else {
1245+
// We calculate the nonlinear probabilities using floats anyway, so just stub out to
1246+
// the float version and then convert to integers.
1247+
let (num, den) = nonlinear_success_probability(
1248+
total_inflight_amount_msat, min_liquidity_msat, max_liquidity_msat, capacity_msat,
1249+
min_zero_implies_no_successes,
1250+
);
12131251

1214-
if min_zero_implies_no_successes && min_liquidity_msat == 0 &&
1215-
denominator < u64::max_value() / 78
1216-
{
1217-
// If we have no knowledge of the channel, scale probability down by a multiple of ~82%.
1218-
// Note that we prefer to increase the denominator rather than decrease the numerator as
1219-
// the denominator is more likely to be larger and thus provide greater precision. This is
1220-
// mostly an overoptimization but makes a large difference in tests.
1221-
denominator = denominator * 78 / 64
1252+
// Because our numerator and denominator max out at 0.0078125 we need to multiply them
1253+
// by quite a large factor to get something useful (ideally in the 2^30 range).
1254+
const BILLIONISH: f64 = 1024.0 * 1024.0 * 1024.0 * 64.0;
1255+
let numerator = (num * BILLIONISH) as u64 + 1;
1256+
let denominator = (den * BILLIONISH) as u64 + 1;
1257+
debug_assert!(numerator <= 1 << 30, "Got large numerator ({}) from float {}.", numerator, num);
1258+
debug_assert!(denominator <= 1 << 30, "Got large denominator ({}) from float {}.", denominator, den);
1259+
(numerator, denominator)
12221260
}
1223-
1224-
(numerator, denominator)
12251261
}
12261262

12271263
impl<L: Deref<Target = u64>, HT: Deref<Target = HistoricalLiquidityTracker>, T: Deref<Target = Duration>>

0 commit comments

Comments
 (0)