mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-14 12:38:13 -07:00
global: snapshot
This commit is contained in:
@@ -5,7 +5,7 @@ use vecdb::{AnyStoredVec, AnyVec, Database, EagerVec, Exit, PcoVec, ReadableVec,
|
||||
|
||||
use crate::{
|
||||
ComputeIndexes, blocks, indexes,
|
||||
internal::{ComputedFromHeightStdDevExtended, Price},
|
||||
internal::{ComputedFromHeightStdDevExtended, Price, TDigest},
|
||||
};
|
||||
|
||||
use super::super::ComputedFromHeight;
|
||||
@@ -31,9 +31,12 @@ pub struct ComputedFromHeightRatioExtension<M: StorageMode = Rw> {
|
||||
pub ratio_4y_sd: ComputedFromHeightStdDevExtended<M>,
|
||||
pub ratio_2y_sd: ComputedFromHeightStdDevExtended<M>,
|
||||
pub ratio_1y_sd: ComputedFromHeightStdDevExtended<M>,
|
||||
|
||||
#[traversable(skip)]
|
||||
tdigest: TDigest,
|
||||
}
|
||||
|
||||
const VERSION: Version = Version::new(3);
|
||||
const VERSION: Version = Version::new(4);
|
||||
|
||||
impl ComputedFromHeightRatioExtension {
|
||||
pub(crate) fn forced_import(
|
||||
@@ -92,6 +95,7 @@ impl ComputedFromHeightRatioExtension {
|
||||
ratio_pct5_price: import_price!("ratio_pct5"),
|
||||
ratio_pct2_price: import_price!("ratio_pct2"),
|
||||
ratio_pct1_price: import_price!("ratio_pct1"),
|
||||
tdigest: TDigest::default(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -118,8 +122,6 @@ impl ComputedFromHeightRatioExtension {
|
||||
exit,
|
||||
)?;
|
||||
|
||||
// Percentiles via order-statistic Fenwick tree with coordinate compression.
|
||||
// O(n log n) total vs O(n²) for the naive sorted-insert approach.
|
||||
let ratio_version = ratio_source.version();
|
||||
self.mut_ratio_vecs()
|
||||
.try_for_each(|v| -> Result<()> {
|
||||
@@ -138,53 +140,19 @@ impl ComputedFromHeightRatioExtension {
|
||||
let ratio_len = ratio_source.len();
|
||||
|
||||
if ratio_len > start {
|
||||
let all_ratios = ratio_source.collect_range_at(0, ratio_len);
|
||||
|
||||
// Coordinate compression: unique sorted values → integer ranks
|
||||
let coords = {
|
||||
let mut c = all_ratios.clone();
|
||||
c.sort_unstable();
|
||||
c.dedup();
|
||||
c
|
||||
};
|
||||
let m = coords.len();
|
||||
|
||||
// Build Fenwick tree (BIT) from elements [0, start) in O(m)
|
||||
let mut bit = vec![0u32; m + 1]; // 1-indexed
|
||||
for &v in &all_ratios[..start] {
|
||||
bit[coords.binary_search(&v).unwrap() + 1] += 1;
|
||||
}
|
||||
for i in 1..=m {
|
||||
let j = i + (i & i.wrapping_neg());
|
||||
if j <= m {
|
||||
bit[j] += bit[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Highest power of 2 <= m (for binary-lifting kth query)
|
||||
let log2 = {
|
||||
let mut b = 1usize;
|
||||
while b <= m {
|
||||
b <<= 1;
|
||||
}
|
||||
b >> 1
|
||||
};
|
||||
|
||||
// Find rank of k-th smallest element (k is 1-indexed) in O(log m)
|
||||
let kth = |bit: &[u32], mut k: u32| -> usize {
|
||||
let mut pos = 0;
|
||||
let mut b = log2;
|
||||
while b > 0 {
|
||||
let next = pos + b;
|
||||
if next <= m && bit[next] < k {
|
||||
k -= bit[next];
|
||||
pos = next;
|
||||
let tdigest_count = self.tdigest.count() as usize;
|
||||
if tdigest_count != start {
|
||||
self.tdigest.reset();
|
||||
if start > 0 {
|
||||
let historical = ratio_source.collect_range_at(0, start);
|
||||
for &v in &historical {
|
||||
self.tdigest.add(*v as f64);
|
||||
}
|
||||
b >>= 1;
|
||||
}
|
||||
pos
|
||||
};
|
||||
}
|
||||
|
||||
// Process new blocks [start, ratio_len)
|
||||
let new_ratios = ratio_source.collect_range_at(start, ratio_len);
|
||||
let mut pct_vecs: [&mut EagerVec<PcoVec<Height, StoredF32>>; 6] = [
|
||||
&mut self.ratio_pct1.height,
|
||||
&mut self.ratio_pct2.height,
|
||||
@@ -194,25 +162,14 @@ impl ComputedFromHeightRatioExtension {
|
||||
&mut self.ratio_pct99.height,
|
||||
];
|
||||
const PCTS: [f64; 6] = [0.01, 0.02, 0.05, 0.95, 0.98, 0.99];
|
||||
let mut out = [0.0f64; 6];
|
||||
|
||||
let mut count = start;
|
||||
for (offset, &ratio) in all_ratios[start..].iter().enumerate() {
|
||||
count += 1;
|
||||
|
||||
// Insert into Fenwick tree: O(log m)
|
||||
let mut i = coords.binary_search(&ratio).unwrap() + 1;
|
||||
while i <= m {
|
||||
bit[i] += 1;
|
||||
i += i & i.wrapping_neg();
|
||||
}
|
||||
|
||||
// Nearest-rank percentile: one kth query each
|
||||
for (offset, &ratio) in new_ratios.iter().enumerate() {
|
||||
self.tdigest.add(*ratio as f64);
|
||||
self.tdigest.quantiles(&PCTS, &mut out);
|
||||
let idx = start + offset;
|
||||
let cf = count as f64;
|
||||
for (vec, &pct) in pct_vecs.iter_mut().zip(PCTS.iter()) {
|
||||
let k = (cf * pct).ceil().max(1.0) as u32;
|
||||
let val = coords[kth(&bit, k)];
|
||||
vec.truncate_push_at(idx, val)?;
|
||||
for (vec, &val) in pct_vecs.iter_mut().zip(out.iter()) {
|
||||
vec.truncate_push_at(idx, StoredF32::from(val as f32))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user