sumset.rs
15.8 kB · rust · 480 lines
1use super::design::elements;2use crate::core::error::{value_error, Result};34/// The deepest level a [`Sumset`] is built to: `S meet [0, 3^20]`, a bit array of `436` MB.5pub const DEEPEST: u32 = 20;67/// The widest level a [`Pair`] names in either base, so `3^k`, `4^m` and every difference string fit a signed 64-bit integer.8pub const WIDEST: u32 = 30;910const BLOCK: usize = 8;1112// THE SUMSET1314/// The sumset `S = A + B` of Erdos problem 125 up to `3^level`: `A` the integers whose base-3 digits are all `0` or `1`, `B` those whose base-4 digits are.15///16/// One bit per integer of `[0, 3^level]`: the members of `A` are set directly and each power `4^j <= 3^level` is folded in by one shift-or pass, `S |= S << 4^j`.17/// A running count every eight words makes `card(S meet [1, x])` one table read and at most eight popcounts.18pub struct Sumset {19 level: u32,20 top: u64,21 words: Vec<u64>,22 runs: Vec<u64>,23}2425impl Sumset {26 /// Builds `S meet [0, 3^level]`.27 ///28 /// ```29 /// let s = mrlyrs::num::sumset::Sumset::new(4).unwrap();30 /// assert_eq!(s.count(81), Some(79));31 /// ```32 ///33 /// # Errors34 ///35 /// Errs when the level is zero or past [`DEEPEST`].36 pub fn new(level: u32) -> Result<Sumset> {37 if !(1..=DEEPEST).contains(&level) {38 return value_error(format!("the level runs from 1 to {DEEPEST}, not {level}."));39 }40 let top = 3u64.pow(level);41 let mut words = vec![0u64; (top / 64 + 1) as usize];42 for a in elements(3, &[0, 1], level as usize)43 .into_iter()44 .chain([0, top])45 {46 words[(a / 64) as usize] |= 1 << (a % 64);47 }48 let mut power = 1u64;49 while power <= top {50 shift_or(&mut words, power);51 power *= 4;52 }53 let spare = top % 64;54 if spare != 63 {55 let last = words.len() - 1;56 words[last] &= (1u64 << (spare + 1)) - 1;57 }58 let mut runs = Vec::with_capacity(words.len() / BLOCK + 2);59 let mut total = 0u64;60 for chunk in words.chunks(BLOCK) {61 runs.push(total);62 total += chunk.iter().map(|w| u64::from(w.count_ones())).sum::<u64>();63 }64 runs.push(total);65 Ok(Sumset {66 level,67 top,68 words,69 runs,70 })71 }7273 /// The level the array was built to.74 pub fn level(&self) -> u32 {75 self.level76 }7778 /// The largest integer the array holds, `3^level`.79 pub fn top(&self) -> u64 {80 self.top81 }8283 /// Whether `x` is in `S`, or `None` past the top.84 pub fn contains(&self, x: u64) -> Option<bool> {85 (x <= self.top).then(|| self.words[(x / 64) as usize] >> (x % 64) & 1 == 1)86 }8788 /// Counts `card(S meet [1, x])`, or `None` past the top.89 pub fn count(&self, x: u64) -> Option<u64> {90 (x <= self.top).then(|| self.upto(x) - 1)91 }9293 /// Reads the density `D(x) = card(S meet [1, x])/x`, or `None` at zero and past the top.94 pub fn density(&self, x: u64) -> Option<f64> {95 (1..=self.top)96 .contains(&x)97 .then(|| (self.upto(x) - 1) as f64 / x as f64)98 }99100 /// Reads the share of members in each of `cells` equal runs of the integers `[low, high)`, the strip of `S` a page draws.101 ///102 /// Run `i` is `[low + floor(i n/cells), low + floor((i + 1) n/cells))` with `n = high - low`.103 ///104 /// # Errors105 ///106 /// Errs when the window is empty or runs past the top, or when `cells` is zero or more than the integers in the window.107 pub fn fills(&self, low: u64, high: u64, cells: usize) -> Result<Vec<f64>> {108 if low >= high || high > self.top + 1 {109 return value_error(format!(110 "the window [{low}, {high}) must be a nonempty part of [0, {}].",111 self.top112 ));113 }114 let span = high - low;115 if cells == 0 || cells as u64 > span {116 return value_error(format!(117 "a window of {span} integers splits into 1 to {span} cells, not {cells}."118 ));119 }120 let edge = |i: u64| low + (u128::from(i) * u128::from(span) / cells as u128) as u64;121 Ok((0..cells as u64)122 .map(|i| {123 let (a, b) = (edge(i), edge(i + 1));124 (self.below(b) - self.below(a)) as f64 / (b - a) as f64125 })126 .collect())127 }128129 /// Reads the least and the greatest `D(x)` over each window `[edges[i], edges[i + 1])`, `None` for an empty window.130 ///131 /// # Errors132 ///133 /// Errs when the edges decrease, start at zero or run past `top + 1`.134 pub fn extremes(&self, edges: &[u64]) -> Result<Vec<Option<(f64, f64)>>> {135 if edges.first() == Some(&0)136 || edges.last().is_some_and(|&e| e > self.top + 1)137 || edges.windows(2).any(|w| w[0] > w[1])138 {139 return value_error(format!(140 "the edges must climb inside [1, {}].",141 self.top + 1142 ));143 }144 Ok(edges145 .windows(2)146 .map(|w| {147 let (a, b) = (w[0], w[1]);148 if a == b {149 return None;150 }151 let mut members = self.below(a);152 let (mut low, mut high) = (f64::INFINITY, f64::NEG_INFINITY);153 for x in a..b {154 members += self.words[(x / 64) as usize] >> (x % 64) & 1;155 let d = (members - 1) as f64 / x as f64;156 low = low.min(d);157 high = high.max(d);158 }159 Some((low, high))160 })161 .collect())162 }163164 fn upto(&self, x: u64) -> u64 {165 self.below(x + 1)166 }167168 fn below(&self, x: u64) -> u64 {169 let word = (x / 64) as usize;170 let block = word / BLOCK;171 let whole: u64 = self.words[block * BLOCK..word]172 .iter()173 .map(|w| u64::from(w.count_ones()))174 .sum();175 let part = match (x % 64, self.words.get(word)) {176 (0, _) | (_, None) => 0,177 (bits, Some(w)) => u64::from((w & ((1u64 << bits) - 1)).count_ones()),178 };179 self.runs[block] + whole + part180 }181}182183fn shift_or(words: &mut [u64], shift: u64) {184 let (jump, bits) = ((shift / 64) as usize, (shift % 64) as u32);185 for i in (jump..words.len()).rev() {186 let from = i - jump;187 let mut v = words[from] << bits;188 if bits > 0 && from > 0 {189 v |= words[from - 1] >> (64 - bits);190 }191 words[i] |= v;192 }193}194195// THE PAIRS196197/// A pair of levels: the base-3 level `A_k = A meet [0, 3^k)` against the base-4 level `B_m = B meet [0, 4^m)`, `k` the field `three` and `m` the field `four`.198#[derive(Clone, Copy, Debug, PartialEq, Eq)]199pub struct Pair {200 /// The base-3 level `k`.201 pub three: u32,202 /// The base-4 level `m`.203 pub four: u32,204}205206impl Pair {207 /// Names the pair `(k, m)`.208 ///209 /// # Errors210 ///211 /// Errs when either level is zero or past [`WIDEST`].212 pub fn new(three: u32, four: u32) -> Result<Pair> {213 if !(1..=WIDEST).contains(&three) || !(1..=WIDEST).contains(&four) {214 return value_error(format!(215 "both levels run from 1 to {WIDEST}, not ({three}, {four})."216 ));217 }218 Ok(Pair { three, four })219 }220221 fn powers(&self) -> (u64, u64) {222 (3u64.pow(self.three), 4u64.pow(self.four))223 }224225 /// The largest element `d(k, m) = (3^k - 1)/2 + (4^m - 1)/3` of `A_k + B_m`.226 pub fn largest(&self) -> u64 {227 let (p, q) = self.powers();228 (p - 1) / 2 + (q - 1) / 3229 }230231 /// The first and the last integer of the open interval `(d(k, m), min(3^k, 4^m))`, which `S` misses, or `None` when it holds none.232 ///233 /// A sum with `a < 3^k` and `b < 4^m` is at most `d(k, m)`, and a sum with `a >= 3^k` or `b >= 4^m` is at least `min(3^k, 4^m)`.234 pub fn gap(&self) -> Option<(u64, u64)> {235 let (p, q) = self.powers();236 let d = self.largest();237 (p.min(q) > d + 1).then(|| (d + 1, p.min(q) - 1))238 }239240 /// Whether the pair is clean, `3^k > d(k, m)` and `4^m > d(k, m)`, so that `S meet [0, d] = A_k + B_m`.241 pub fn clean(&self) -> bool {242 let (p, q) = self.powers();243 p.min(q) > self.largest()244 }245246 /// Whether the pair is a gap copy, `2 4^m < 3^k + 5`: `A_k + B_m` is then two disjoint translates of `A_(k-1) + B_m` and its energy is twice theirs.247 pub fn copy(&self) -> bool {248 let (p, q) = self.powers();249 2 * q < p + 5250 }251252 /// The scaling `tau = 4^m/3^k`.253 pub fn scale(&self) -> f64 {254 let (p, q) = self.powers();255 q as f64 / p as f64256 }257258 /// The additive energy `E(k, m) = sum_x r(x)^2`, `r(x)` the number of ways `x = a + b` with `a` in `A_k` and `b` in `B_m`.259 ///260 /// Summed as `sum_t 2^(z_3(t) + z_4(t))` over the `3^m` integers `t` with an `m`-digit base-4 string in `{-1, 0, 1}`, `z_4(t)` its zero digits and `z_3(t)` the zero digits of the `k`-digit balanced ternary expansion of `t`, a `t` past `(3^k - 1)/2` adding nothing: a difference of two digit strings from `{0, 1}` is a digit string from `{-1, 0, 1}`, which fixes `t` in either base, and each zero difference arises twice. The cost is `3^m` strings.261 pub fn energy(&self) -> u128 {262 let powers: Vec<i64> = (0..self.four).map(|i| 4i64.pow(i)).collect();263 strings(&powers, 0, 0, 0, self.three)264 }265266 /// The energy ratio `Q(k, m) = E(k, m) (d + 1)/4^(k+m)` of the energy [`Pair::energy`] returns, the energy against its flat value, at least `1`; `card(A_k + B_m) >= (d + 1)/Q` by Cauchy-Schwarz.267 pub fn ratio(&self, energy: u128) -> f64 {268 let flat = 4f64.powi((self.three + self.four) as i32);269 energy as f64 * (self.largest() + 1) as f64 / flat270 }271}272273fn strings(powers: &[i64], at: usize, t: i64, zeros: u32, three: u32) -> u128 {274 if at == powers.len() {275 return balanced_zeros(t, three).map_or(0, |z| 1u128 << (z + zeros));276 }277 (-1..=1)278 .map(|digit| {279 strings(280 powers,281 at + 1,282 t + digit * powers[at],283 zeros + u32::from(digit == 0),284 three,285 )286 })287 .sum()288}289290fn balanced_zeros(mut t: i64, digits: u32) -> Option<u32> {291 let mut zeros = 0;292 for _ in 0..digits {293 let r = t.rem_euclid(3);294 zeros += u32::from(r == 0);295 t = (t - r) / 3 + i64::from(r == 2);296 }297 (t == 0).then_some(zeros)298}299300/// Lists the pairs of the census: every `(k, m)` with `4^m` within a factor `3` of `3^k` and `d(k, m) <= 3^level`, by `d`.301///302/// # Errors303///304/// Errs when the level is zero or past [`WIDEST`].305pub fn pairs(level: u32) -> Result<Vec<Pair>> {306 if !(1..=WIDEST).contains(&level) {307 return value_error(format!("the level runs from 1 to {WIDEST}, not {level}."));308 }309 let top = 3u64.pow(level);310 let mut out = Vec::new();311 for three in 1..=level {312 for four in 1..=WIDEST {313 let pair = Pair { three, four };314 let (p, q) = pair.powers();315 if q >= 3 * p {316 break;317 }318 if p < 3 * q && pair.largest() <= top {319 out.push(pair);320 }321 }322 }323 out.sort_by_key(Pair::largest);324 Ok(out)325}326327#[cfg(test)]328mod tests {329 use super::*;330331 fn level(k: u32) -> Vec<u64> {332 let mut out = elements(3, &[0, 1], k as usize);333 out.push(0);334 out335 }336337 fn quarter(m: u32) -> Vec<u64> {338 let mut out = elements(4, &[0, 1], m as usize);339 out.push(0);340 out341 }342343 fn histogram(pair: Pair) -> u128 {344 let mut r = vec![0u128; pair.largest() as usize + 1];345 for a in level(pair.three) {346 for b in quarter(pair.four) {347 r[(a + b) as usize] += 1;348 }349 }350 r.iter().map(|v| v * v).sum()351 }352353 fn up(pair: Pair) -> u128 {354 let flat = 4u128.pow(pair.three + pair.four);355 (pair.energy() * u128::from(pair.largest() + 1) * 1_000_000).div_ceil(flat)356 }357358 #[test]359 fn the_array_is_the_double_loop() {360 let s = Sumset::new(8).unwrap();361 let mut sums = vec![false; s.top() as usize + 1];362 for a in level(9) {363 for b in quarter(7) {364 if a + b <= s.top() {365 sums[(a + b) as usize] = true;366 }367 }368 }369 for (x, &member) in sums.iter().enumerate() {370 assert_eq!(s.contains(x as u64), Some(member), "x = {x}");371 }372 assert_eq!(373 s.count(s.top()),374 Some(sums.iter().filter(|&&v| v).count() as u64 - 1)375 );376 assert_eq!(s.contains(s.top() + 1), None);377 }378379 #[test]380 fn the_first_non_members_are_a367090() {381 let s = Sumset::new(6).unwrap();382 let missed: Vec<u64> = (0..=480)383 .filter(|&x| s.contains(x) == Some(false))384 .collect();385 let mut want = vec![62, 63, 143, 144];386 want.extend(207..=242);387 want.extend(463..=480);388 assert_eq!(missed, want);389 }390391 #[test]392 fn the_densities_at_the_powers_of_three_read_as_printed() {393 let s = Sumset::new(10).unwrap();394 let read: Vec<u64> = (4..=10)395 .map(|k| {396 let x = 3u64.pow(k);397 s.count(x).unwrap() * 1_000_000 / x398 })399 .collect();400 assert_eq!(401 read,402 [975308, 835390, 858710, 887517, 908855, 864959, 778472]403 );404 }405406 #[test]407 fn the_window_extremes_read_as_printed() {408 let s = Sumset::new(10).unwrap();409 let edges: Vec<u64> = (5..=10).map(|k| 3u64.pow(k)).collect();410 let six = |v: f64| (v * 1e6).floor() as u64;411 let read = s.extremes(&edges).unwrap();412 let lows: Vec<u64> = read.iter().map(|w| six(w.unwrap().0)).collect();413 let highs: Vec<u64> = read.iter().map(|w| six(w.unwrap().1)).collect();414 assert_eq!(lows, [835390, 852729, 887517, 858945, 778468]);415 assert_eq!(highs, [913419, 903768, 912038, 931596, 913781]);416 }417418 #[test]419 fn the_strip_is_the_share_of_members() {420 let s = Sumset::new(6).unwrap();421 let strip = s.fills(200, 250, 5).unwrap();422 assert_eq!(strip, [0.7, 0.0, 0.0, 0.0, 0.7]);423 assert!(s.fills(0, 731, 5).is_err() && s.fills(10, 12, 3).is_err());424 }425426 #[test]427 fn the_energy_is_the_representation_histogram() {428 for (k, m) in [(3, 2), (4, 3), (5, 4), (6, 5), (7, 5), (9, 7)] {429 let pair = Pair::new(k, m).unwrap();430 assert_eq!(pair.energy(), histogram(pair), "({k}, {m})");431 }432 }433434 #[test]435 fn the_energy_ratios_read_as_printed() {436 let read: Vec<u128> = pairs(22)437 .unwrap()438 .into_iter()439 .filter(|p| p.three >= 6)440 .take(4)441 .map(up)442 .collect();443 assert_eq!(read, [1467705, 1638125, 1664808, 1724517]);444 }445446 #[test]447 fn the_census_holds_twenty_seven_pairs() {448 let census: Vec<Pair> = pairs(22)449 .unwrap()450 .into_iter()451 .filter(|p| p.three >= 6)452 .collect();453 assert_eq!(census.len(), 27);454 assert!(!census.contains(&Pair::new(22, 18).unwrap()));455 }456457 #[test]458 fn a_gap_holds_no_member() {459 let s = Sumset::new(12).unwrap();460 let mut seen = 0;461 for pair in pairs(12).unwrap() {462 if let Some((a, b)) = pair.gap() {463 assert!(pair.clean());464 seen += 1;465 assert!((a..=b).all(|x| s.contains(x) == Some(false)));466 assert_eq!(s.contains(a - 1), Some(true));467 }468 }469 assert!(seen >= 4);470 }471472 #[test]473 fn a_gap_copy_doubles_the_energy() {474 for (k, m) in [(6, 4), (7, 5), (11, 8), (12, 9)] {475 let pair = Pair::new(k, m).unwrap();476 assert!(pair.copy());477 assert_eq!(pair.energy(), 2 * Pair::new(k - 1, m).unwrap().energy());478 }479 }480}