powder.rs
4.2 kB · rust · 136 lines
1use mrlycore::Tensor;2use mrlynum::fft::fft2;34pub struct Powder {5 pub slope: f64,6 pub low: f64,7 pub high: f64,8 pub swing: f64,9}1011fn rings(grid: &Tensor, pad: usize, bins: usize) -> Vec<(f64, f64, f64)> {12 let side = grid.shape[0];13 let bytes = grid.bytes();14 let mut re = vec![0.0f64; pad * pad];15 let mut im = vec![0.0f64; pad * pad];16 for row in 0..side {17 for col in 0..side {18 re[row * pad + col] = bytes[row * side + col] as f64;19 }20 }21 fft2(&mut re, &mut im, pad, false);22 let half = (pad / 2) as i64;23 let top = (half as f64 * 2f64.sqrt()).ln();24 let mut sums = vec![0.0f64; bins];25 let mut logs = vec![0.0f64; bins];26 let mut hits = vec![0.0f64; bins];27 for row in 0..pad {28 for col in 0..pad {29 let u = if (row as i64) <= half {30 row as i6431 } else {32 row as i64 - pad as i6433 };34 let v = if (col as i64) <= half {35 col as i6436 } else {37 col as i64 - pad as i6438 };39 let norm = ((u * u + v * v) as f64).sqrt();40 if norm < 1.0 {41 continue;42 }43 let bin = ((norm.ln() / top) * bins as f64) as usize;44 if bin >= bins {45 continue;46 }47 let flat = row * pad + col;48 let power = re[flat] * re[flat] + im[flat] * im[flat];49 sums[bin] += power;50 logs[bin] += power.max(1e-300).ln();51 hits[bin] += 1.0;52 }53 }54 let mut out = Vec::new();55 for bin in 0..bins {56 if hits[bin] == 0.0 {57 continue;58 }59 let centre = ((bin as f64 + 0.5) / bins as f64 * top).exp();60 out.push((centre, sums[bin] / hits[bin], (logs[bin] / hits[bin]).exp()));61 }62 out63}6465fn fit(points: &[(f64, f64)]) -> (f64, f64) {66 let n = points.len() as f64;67 let sx: f64 = points.iter().map(|p| p.0.ln()).sum();68 let sy: f64 = points.iter().map(|p| p.1.ln()).sum();69 let sxx: f64 = points.iter().map(|p| p.0.ln() * p.0.ln()).sum();70 let sxy: f64 = points.iter().map(|p| p.0.ln() * p.1.ln()).sum();71 let slope = (n * sxy - sx * sy) / (n * sxx - sx * sx);72 (slope, (sy - slope * sx) / n)73}7475fn slide(points: &[(f64, f64)], width: f64, step: f64) -> (f64, f64) {76 let first = points.first().map(|p| p.0.ln()).unwrap_or(0.0);77 let last = points.last().map(|p| p.0.ln()).unwrap_or(0.0);78 let mut low = f64::INFINITY;79 let mut high = f64::NEG_INFINITY;80 let mut start = first;81 while start + width <= last + 1e-12 {82 let window: Vec<(f64, f64)> = points83 .iter()84 .copied()85 .filter(|(k, _)| k.ln() >= start && k.ln() <= start + width)86 .collect();87 if window.len() >= 8 {88 let (value, _) = fit(&window);89 low = low.min(value);90 high = high.max(value);91 }92 start += step;93 }94 (low, high)95}9697pub fn powder(98 grid: &Tensor,99 pad: usize,100 low: f64,101 high: f64,102 bins: usize,103 phases: usize,104) -> Powder {105 let all = rings(grid, pad, bins);106 let band: Vec<(f64, f64, f64)> = all107 .iter()108 .copied()109 .filter(|(k, p, g)| *k >= low && *k <= high && *p > 0.0 && *g > 0.0)110 .collect();111 let arithmetic: Vec<(f64, f64)> = band.iter().map(|(k, p, _)| (*k, *p)).collect();112 let (slope, intercept) = fit(&arithmetic);113 let period = (crate::design::BASE as f64).ln();114 let (least, most) = slide(&arithmetic, 3.0 * period, period / 4.0);115 let mut sums = vec![0.0f64; phases];116 let mut hits = vec![0.0f64; phases];117 for (k, power) in &arithmetic {118 let residual = power.ln() - slope * k.ln() - intercept;119 let phase = (k.ln() / period).rem_euclid(1.0);120 let bin = ((phase * phases as f64) as usize).min(phases - 1);121 sums[bin] += residual;122 hits[bin] += 1.0;123 }124 let curve: Vec<f64> = sums125 .iter()126 .zip(&hits)127 .map(|(sum, hit)| if *hit == 0.0 { f64::NAN } else { sum / hit })128 .collect();129 let swing = crate::mass::spread(&curve);130 Powder {131 slope,132 low: least,133 high: most,134 swing,135 }136}