class v5 acceptance (c'''): the per-site distinct-index floor raised to 0.995 on the ratio pass's own run, keyed on the state flag; known-failed first on adv-accept's seed 100767 (program 9d68e6286fc817d4, site 6 at 0.9919), the census test behind the floor
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
8674880135
commit
a2d333d654
2 changed files with 278 additions and 23 deletions
|
|
@ -9,6 +9,7 @@
|
|||
//! | (a) static | for every `load`, some instruction between the previous `load` from the same source register and this one, in cyclic order over the 64 instructions, writes that register |
|
||||
//! | (b) static | every register `r0..r7` is the destination of at least one `add`, `sub`, `xor`, `mad`, `shfl` or `load` |
|
||||
//! | (c) dynamic | the program is interpreted for [`ACCEPT_UNITS`] (64) units of 32 lanes at base nonces drawn from SplitMix64 seeded with `FNV-1a-64("igneum-accept/" \|\| seed words as little-endian bytes)`, each `low32(next()) AND NOT 31`, with init words equal to the seed words and the closed-form dataset `dataset_elem(idx, S[0], S[1])` at [`ACCEPT_DATASET_LOG2`] (2^28 words) in place of the memory-hard dataset. Over the 2,048 evaluations: no register has a bit equal in every final value; no load site (iteration, instruction) reads one address in all 32 lanes of any unit; fewer than [`MAX_SATURATED`] (164, 1 percent of 16,384) final register values are 0 or 2^32 - 1; every output bit's ones count is within [`BIAS_TOLERANCE`] (136, 6 sigma) of 1,024; the distinct masked addresses read by one lane in one evaluation, summed over the 2,048 evaluations, exceed [`MIN_DISTINCT_SUM`] (245,760, a mean above 120 of the 128 loads) |
|
||||
//! | (c''') class v5 | the per-site distinct-index floor of (c'') raised to [`MIN_DISTINCT_RATIO_V5`] (0.995) on the same 2^20-evaluation run, keyed on the state flag; the ratio at the 0.98 floor is still named (c'') first (`docs/design/class-v5-stored-state.md` section 14) |
|
||||
//!
|
||||
//! The dynamic test uses the closed form so that it is a pure function of the program (no cache, no day) and
|
||||
//! costs about a millisecond on one core. A hot-table load (`docs/plans/hot-table.md`) reads the closed form keyed by
|
||||
|
|
@ -40,6 +41,18 @@ pub const MIN_DISTINCT_RATIO_V4: f64 = 0.98;
|
|||
/// Kept for the record and the driver, not wired: the most repeated source value per site over the (c) units'
|
||||
/// 16,384 evaluations (a uniform site repeats a value 2 or 3 times; the finding's bands sit under the ratio instead).
|
||||
pub const MAX_SOURCE_REPEAT_V4: u32 = 8;
|
||||
/// (c'''), class v5 (`docs/design/class-v5-stored-state.md` section 14): the per-site distinct-index floor of the
|
||||
/// ratio pass raised from sub-version 3's 0.98 to the bottom of the clean seeds' spread. On the 2^20-evaluation
|
||||
/// sample the model's spread is about 0.0001 (the collision count is near Poisson with mean n^2 / 2W = 8,192 on
|
||||
/// the quarter window); the sub-version 3 clean seeds' site rows read 0.9960 at the minimum, 0.9990 at p1 and
|
||||
/// 1.0000 at the median (adv-accept's census), and the residual class the in-house pass attributed (seed 100767,
|
||||
/// program 9d68e6286fc817d4: site 6 reads the multiples of 2^19 through `rotl(x * stride, rot)` of a `mad` value
|
||||
/// that is near zero in a value class, 3.35 percent of its live reads on the top 0.1 percent of items, 1,677 reads
|
||||
/// of index 0 in 10^6 nonces) reads 0.9919 closed and 0.9920 on the live day at the same sample: 0.012 above the
|
||||
/// 0.98 floor, 0.003 under this one. The floor is the cheapest fix that reaches the class (main's order of 7 October
|
||||
/// 2026): no new sample, the verdict at attempt 2 with no 2^24 run; the census test `v5_hot_census` reads its clean
|
||||
/// rejection rate and the attempts histogram beside the per-site hot-item test.
|
||||
pub const MIN_DISTINCT_RATIO_V5: f64 = 0.995;
|
||||
/// Hashes the dynamic test evaluates: 2,048.
|
||||
pub const ACCEPT_HASHES: usize = ACCEPT_UNITS * LANES;
|
||||
/// Domain tag of the base-nonce stream.
|
||||
|
|
@ -88,6 +101,11 @@ pub enum Reject {
|
|||
/// `evaluations`, `ratio_milli` / 1000 of a uniform source on its window, under the floor: a low-entropy index band
|
||||
/// (F8's p23, p18, p19, p15, p56).
|
||||
LowEntropySite { site: u8, distinct: u32, evaluations: u32, ratio_milli: u32 },
|
||||
/// (c'''), class v5: the load at `site` read `distinct` distinct dataset word indices over `evaluations`,
|
||||
/// `ratio_milli` / 1000 of the window model, under [`MIN_DISTINCT_RATIO_V5`] (and at or above sub-version 3's
|
||||
/// floor, else (c'') names it); `top_index` was its most read index, `top_count` times: a hot set from a
|
||||
/// value-level constant upstream of the site that neither the lineage rule nor the 0.98 floor reaches (seed 100767).
|
||||
HotItemSite { site: u8, distinct: u32, evaluations: u32, ratio_milli: u32, top_index: u32, top_count: u32 },
|
||||
/// (c): output bit `bit` was set in `ones` of 2,048 hashes.
|
||||
OutputBias { bit: u8, ones: u32 },
|
||||
/// (c): the distinct-address sum was `sum`.
|
||||
|
|
@ -108,6 +126,7 @@ impl std::fmt::Display for Reject {
|
|||
Reject::Saturated { count } => write!(f, "(c) {count} of 16384 final register values saturated (limit 163)"),
|
||||
Reject::UnfreshLoadSource { instr, reg } => write!(f, "(a') load at {instr} reads r{reg}, not fresh by dataflow in the loop's steady state (class v4 sub-version 2)"),
|
||||
Reject::RepeatedSource { site, value, count } => write!(f, "(c'') load site {site} read the value {value:#010x} in {count} of 16384 evaluations (limit {})", MAX_SOURCE_REPEAT_V4 - 1),
|
||||
Reject::HotItemSite { site, distinct, evaluations, ratio_milli, top_index, top_count } => write!(f, "(c''') load site {site} read {distinct} distinct word indices over {evaluations} evaluations, {ratio_milli}/1000 of the window model, under the class v5 floor (most read: index {top_index:#x}, {top_count} times): a hot set from a value-level constant upstream"),
|
||||
Reject::LowEntropySite { site, distinct, evaluations, ratio_milli } => write!(f, "(c'') load site {site} read {distinct} distinct word indices over {evaluations} evaluations, {}.{:03} of a uniform source on its window (floor {MIN_DISTINCT_RATIO_V4} at 2^20)", ratio_milli / 1000, ratio_milli % 1000),
|
||||
Reject::SaturatedSource { site, count } => write!(f, "(c') load site {site} read a saturated source value in {count} of 16384 evaluations (limit 163)"),
|
||||
Reject::OutputBias { bit, ones } => write!(f, "(c) output bit {bit} set in {ones} of 2048 hashes"),
|
||||
|
|
@ -195,31 +214,37 @@ pub fn check_distinct_indices_v4(p: &Program) -> Result<(), Reject> {
|
|||
/// window (`N - N^2 / 2W`, the window `2^28 >> min(win, 2)` words of the closed-form dataset), `Err` at the first
|
||||
/// site under `floor`, else the minimum ratio and its site.
|
||||
pub fn distinct_ratio_pass(p: &Program, units: usize, floor: f64) -> Result<(f64, usize), Reject> {
|
||||
let n = (units * LANES * ITERATIONS) as f64;
|
||||
let d = distinct_indices_v4(p, units)?;
|
||||
let mut min = (f64::MAX, 0usize);
|
||||
let mut site = 0usize;
|
||||
for i in &p.instrs {
|
||||
if !i.op.is_load() {
|
||||
continue;
|
||||
}
|
||||
let wsize = ((1u64 << ACCEPT_DATASET_LOG2) >> (i.win as u64).min(2)) as f64;
|
||||
let ratio = d[site] as f64 / (n - n * n / (2.0 * wsize));
|
||||
if ratio < floor {
|
||||
return Err(Reject::LowEntropySite { site: site as u8, distinct: d[site], evaluations: n as u32, ratio_milli: (ratio * 1000.0) as u32 });
|
||||
}
|
||||
if ratio < min.0 {
|
||||
min = (ratio, site);
|
||||
}
|
||||
site += 1;
|
||||
}
|
||||
Ok(min)
|
||||
let stats = site_index_stats(p, units)?;
|
||||
distinct_ratio_on(p, &stats, units, floor)
|
||||
}
|
||||
|
||||
/// The distinct dataset word indices every load site reads over `units` units of the seed's acceptance stream on
|
||||
/// the closed-form words (the sample caps the count near `units x 32 x 8`, so a site's index entropy is read only
|
||||
/// below about log2 of that).
|
||||
pub fn distinct_indices_v4(p: &Program, units: usize) -> Result<Vec<u32>, Reject> {
|
||||
Ok(site_index_stats(p, units)?.into_iter().map(|s| s.distinct).collect())
|
||||
}
|
||||
|
||||
/// What one load site's word indices over the ratio pass look like: the distinct count (c'' and c'''), the sum of
|
||||
/// `C(run, 2)` over the indices (the colliding pairs), and the most read index with its count (the diagnostic the
|
||||
/// reject names).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct SiteIndexStats {
|
||||
pub distinct: u32,
|
||||
pub pairs: u64,
|
||||
pub top_index: u32,
|
||||
pub top_count: u32,
|
||||
}
|
||||
|
||||
/// The window of a load site in words at the rule's dataset: `2^28 >> min(win, 2)`.
|
||||
pub fn site_window_words(ins: &Instr) -> u64 {
|
||||
(1u64 << ACCEPT_DATASET_LOG2) >> (ins.win as u64).min(2)
|
||||
}
|
||||
|
||||
/// One interpreter run over `units` units with every load site's word indices kept, then per site the sorted
|
||||
/// run lengths: [`SiteIndexStats`] per site in load order. The ratio pass (c'') and the hot-item rule (c''') read
|
||||
/// the same run, so class v5 pays the sort once.
|
||||
pub fn site_index_stats(p: &Program, units: usize) -> Result<Vec<SiteIndexStats>, Reject> {
|
||||
let loads = p.loads_per_hash();
|
||||
let sites = loads / ITERATIONS;
|
||||
let mut acc = Acc {
|
||||
|
|
@ -239,12 +264,89 @@ pub fn distinct_indices_v4(p: &Program, units: usize) -> Result<Vec<u32>, Reject
|
|||
let mut out = Vec::with_capacity(sites);
|
||||
for ix in acc.indices.take().unwrap().iter_mut() {
|
||||
ix.sort_unstable();
|
||||
ix.dedup();
|
||||
out.push(ix.len() as u32);
|
||||
let mut st = SiteIndexStats { distinct: 0, pairs: 0, top_index: 0, top_count: 0 };
|
||||
let mut i = 0;
|
||||
while i < ix.len() {
|
||||
let mut j = i + 1;
|
||||
while j < ix.len() && ix[j] == ix[i] {
|
||||
j += 1;
|
||||
}
|
||||
let run = (j - i) as u32;
|
||||
st.distinct += 1;
|
||||
st.pairs += (run as u64) * (run as u64 - 1) / 2;
|
||||
if run > st.top_count {
|
||||
st.top_count = run;
|
||||
st.top_index = ix[i];
|
||||
}
|
||||
i = j;
|
||||
}
|
||||
out.push(st);
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// (c'''), class v5: the raised floor on the ratio pass's sample, `Err` at the first load site under `floor`, else
|
||||
/// the minimum ratio and its site. Pure in the program as (c'') is (the closed form, the seed's acceptance stream);
|
||||
/// the state leaves move the dataset's words, not the indices a site reads (adv-accept read the exemplar's site 6
|
||||
/// at 0.9919 closed and 0.9920 on the live day).
|
||||
pub fn hot_item_pass(p: &Program, stats: &[SiteIndexStats], units: usize, floor: f64) -> Result<(f64, usize), Reject> {
|
||||
let n = (units * LANES * ITERATIONS) as f64;
|
||||
let mut min = (f64::MAX, 0usize);
|
||||
let mut site = 0usize;
|
||||
for i in &p.instrs {
|
||||
if !i.op.is_load() {
|
||||
continue;
|
||||
}
|
||||
let st = &stats[site];
|
||||
let ratio = site_ratio(st.distinct, n, site_window_words(i));
|
||||
if ratio < floor {
|
||||
return Err(Reject::HotItemSite { site: site as u8, distinct: st.distinct, evaluations: n as u32, ratio_milli: (ratio * 1000.0) as u32, top_index: st.top_index, top_count: st.top_count });
|
||||
}
|
||||
if ratio < min.0 {
|
||||
min = (ratio, site);
|
||||
}
|
||||
site += 1;
|
||||
}
|
||||
Ok(min)
|
||||
}
|
||||
|
||||
/// A site's distinct-index ratio against the window model `N - N^2 / 2W` (the (c'') form, kept so the census
|
||||
/// numbers of sub-version 3 read on the same scale).
|
||||
pub fn site_ratio(distinct: u32, n: f64, window: u64) -> f64 {
|
||||
distinct as f64 / (n - n * n / (2.0 * window as f64))
|
||||
}
|
||||
|
||||
/// The ratio floor (c'') on precomputed site stats: `Err` at the first site under `floor`, else the minimum ratio
|
||||
/// and its site (the pass [`distinct_ratio_pass`] runs when it has no stats yet).
|
||||
pub fn distinct_ratio_on(p: &Program, stats: &[SiteIndexStats], units: usize, floor: f64) -> Result<(f64, usize), Reject> {
|
||||
let n = (units * LANES * ITERATIONS) as f64;
|
||||
let mut min = (f64::MAX, 0usize);
|
||||
let mut site = 0usize;
|
||||
for i in &p.instrs {
|
||||
if !i.op.is_load() {
|
||||
continue;
|
||||
}
|
||||
let d = stats[site].distinct;
|
||||
let ratio = site_ratio(d, n, site_window_words(i));
|
||||
if ratio < floor {
|
||||
return Err(Reject::LowEntropySite { site: site as u8, distinct: d, evaluations: n as u32, ratio_milli: (ratio * 1000.0) as u32 });
|
||||
}
|
||||
if ratio < min.0 {
|
||||
min = (ratio, site);
|
||||
}
|
||||
site += 1;
|
||||
}
|
||||
Ok(min)
|
||||
}
|
||||
|
||||
/// (c'') then (c'''), class v5: one 2^20-evaluation run, the ratio floor of sub-version 3 and the hot-item rule on
|
||||
/// it. The order is the module table's: a low-entropy band is named before a hot item on the same site.
|
||||
pub fn check_indices_v5(p: &Program) -> Result<(), Reject> {
|
||||
let stats = site_index_stats(p, ACCEPT_UNITS_DISTINCT_V4)?;
|
||||
distinct_ratio_on(p, &stats, ACCEPT_UNITS_DISTINCT_V4, MIN_DISTINCT_RATIO_V4)?;
|
||||
hot_item_pass(p, &stats, ACCEPT_UNITS_DISTINCT_V4, MIN_DISTINCT_RATIO_V5).map(|_| ())
|
||||
}
|
||||
|
||||
/// Whether `class` is the class v4 shape (the 256-instruction shadow block over the class v3 base, the pass count and
|
||||
/// the era set aside): the shape the sub-version 2 rules (a') and (c') apply to, on every draw path.
|
||||
pub fn is_class_v4_shape(class: &LoadClass) -> bool {
|
||||
|
|
@ -601,8 +703,14 @@ pub fn check_dynamic(p: &Program) -> Result<AcceptReport, Reject> {
|
|||
if let Some((site, &count)) = acc.sat_source.iter().enumerate().find(|(_, &c)| c >= MAX_SATURATED) {
|
||||
return Err(Reject::SaturatedSource { site: site as u8, count });
|
||||
}
|
||||
// (c''), the ratio on the candidate that passed everything else (the draw's last and dearest test)
|
||||
check_distinct_indices_v4(p)?;
|
||||
// (c''), the ratio on the candidate that passed everything else (the draw's last and dearest test); class v5
|
||||
// reads (c''') the hot-item rule on the same run (`docs/design/class-v5-stored-state.md` section 14), keyed
|
||||
// on the state flag so no class v4 verdict moves
|
||||
if p.class.state {
|
||||
check_indices_v5(p)?;
|
||||
} else {
|
||||
check_distinct_indices_v4(p)?;
|
||||
}
|
||||
}
|
||||
let half = (ACCEPT_HASHES / 2) as u32;
|
||||
let mut bias_max = 0u32;
|
||||
|
|
@ -631,6 +739,147 @@ mod tests {
|
|||
use crate::generator::{candidate, candidate_class, generate, generate_class, GeneratorConfig, generate_v1, LoadClass};
|
||||
use crate::verify::{DatasetMode, DatasetSource};
|
||||
|
||||
/// The f8 label space's chain draw (adv-accept's census space): epoch and era seed bytes for seed `k`, and the
|
||||
/// candidate at `attempt` under `base` (the chain's class v4 sub-version 3 draw for `V4_CLASS`, the class v5 draw
|
||||
/// for `V5_CLASS`), stamped as `generate_era` stamps it.
|
||||
fn f8_seed(k: u32) -> (Vec<u8>, Vec<u8>) {
|
||||
use crate::seed::seed_words_from_bytes;
|
||||
let w = |s: String| -> Vec<u8> { seed_words_from_bytes(s.as_bytes()).iter().flat_map(|x| x.to_le_bytes()).collect() };
|
||||
(w(format!("igneum-attack-f8/program/{k}")), w(format!("igneum-attack-f8/era/{k}")))
|
||||
}
|
||||
fn f8_label(epoch: &[u8]) -> String {
|
||||
format!("igneum-epoch/{}", epoch.iter().map(|b| format!("{b:02x}")).collect::<String>())
|
||||
}
|
||||
fn f8_candidate(epoch: &[u8], era: &[u8], attempt: u32, base: LoadClass) -> Program {
|
||||
use crate::generator::{era_generator_of, V3_ALLOWED};
|
||||
let mut p = candidate_class(&f8_label(epoch), epoch, attempt, LoadClass::era(base, era, &V3_ALLOWED));
|
||||
p.generator = era_generator_of(&base);
|
||||
p.era_bytes = Some(era.to_vec());
|
||||
p
|
||||
}
|
||||
fn f8_draw(epoch: &[u8], era: &[u8], base: LoadClass) -> Program {
|
||||
use crate::generator::{generate_era, V3_ALLOWED};
|
||||
generate_era(&f8_label(epoch), epoch, base, era, &V3_ALLOWED)
|
||||
}
|
||||
|
||||
/// (c''') known-failed first (`docs/design/class-v5-stored-state.md` section 14; main's order of 7 October
|
||||
/// 2026): adv-accept's seed 100767 is the chain's class v4 sub-version 3 draw at attempt 2 (program
|
||||
/// 9d68e6286fc817d4), passes every part of the sub-version 3 rule with its site 6 at 0.9919, and reads a hot set
|
||||
/// live (3.35 percent of site 6's reads on the top 0.1 percent of items, the multiples of 2^19). The same shape
|
||||
/// under class v5 is refused at attempt 2 by the raised floor, naming site 6 under 0.995, and the class v5 draw
|
||||
/// moves past it; the class v4 verdict does not move. A clean seed (the genesis draw of both classes) clears
|
||||
/// the floor with room.
|
||||
#[test]
|
||||
fn class_v5_hot_set_rule_known_failed_seed_100767() {
|
||||
use crate::generator::V5_CLASS;
|
||||
let (epoch, era) = f8_seed(100767);
|
||||
let v4 = f8_candidate(&epoch, &era, 2, V4_CLASS);
|
||||
assert_eq!(format!("{:016x}", v4.program_id()), "9d68e6286fc817d4", "the exemplar is the chain's sub-version 3 program");
|
||||
assert_eq!(v4.seed, [0xcc5466bf, 0x375211d6, 0xf22b6e63, 0x638d9f8b, 0x85903c90, 0x03605228, 0x5f97734b, 0x910fb493]);
|
||||
assert!(check(&v4).is_ok(), "class v4 sub-version 3 accepts it: {:?}", check(&v4).err());
|
||||
let v4_stats = site_index_stats(&v4, ACCEPT_UNITS_DISTINCT_V4).unwrap();
|
||||
let (min, site) = distinct_ratio_on(&v4, &v4_stats, ACCEPT_UNITS_DISTINCT_V4, 0.0).unwrap();
|
||||
println!("seed 100767 class v4: min site {site} ratio {min:.4} (distinct {}, most read index {:#x} {} times)", v4_stats[site].distinct, v4_stats[site].top_index, v4_stats[site].top_count);
|
||||
assert_eq!(site, 6);
|
||||
assert!(min > 0.98 && min < 0.995, "the exemplar sits between the floors: {min:.4}");
|
||||
|
||||
let v5 = f8_candidate(&epoch, &era, 2, V5_CLASS);
|
||||
assert_eq!(v5.instrs, v4.instrs, "the same base program under class v5");
|
||||
match check(&v5) {
|
||||
Err(Reject::HotItemSite { site, ratio_milli, distinct, evaluations, top_index, top_count }) => {
|
||||
println!("seed 100767 class v5 attempt 2: (c''') site {site} {distinct} of {evaluations}, ratio {}.{:03}, most read {top_index:#x} x{top_count}", ratio_milli / 1000, ratio_milli % 1000);
|
||||
assert_eq!(site, 6);
|
||||
assert!(ratio_milli >= 980 && ratio_milli < 995);
|
||||
}
|
||||
other => panic!("class v5 must refuse the exemplar at attempt 2 by (c'''), got {other:?}"),
|
||||
}
|
||||
let drawn = f8_draw(&epoch, &era, V5_CLASS);
|
||||
println!("seed 100767 class v5 draw: attempt {} id {:016x}", drawn.attempt, drawn.program_id());
|
||||
assert!(drawn.attempt > 2, "the class v5 draw moves past the exemplar");
|
||||
assert!(check(&drawn).is_ok());
|
||||
let drawn_v4 = f8_draw(&epoch, &era, V4_CLASS);
|
||||
assert_eq!(drawn_v4.attempt, 2, "the class v4 draw still lands on it");
|
||||
|
||||
for (name, p) in [("genesis v4", generate_class("igneum-genesis", V4_CLASS)), ("genesis v5", generate_class("igneum-genesis", V5_CLASS))] {
|
||||
let st = site_index_stats(&p, ACCEPT_UNITS_DISTINCT_V4).unwrap();
|
||||
let (min, site) = distinct_ratio_on(&p, &st, ACCEPT_UNITS_DISTINCT_V4, 0.0).unwrap();
|
||||
println!("{name}: min site {site} ratio {min:.4}");
|
||||
assert!(min >= MIN_DISTINCT_RATIO_V5, "{name}: a clean seed clears the class v5 floor: {min:.4}");
|
||||
}
|
||||
}
|
||||
|
||||
/// The census behind the floor (run by hand on a box: `cargo test --release --lib -- --ignored v5_hot_census
|
||||
/// --nocapture`, `IGNEUM_V5_CENSUS_SEEDS` seeds of the f8 label space from `IGNEUM_V5_CENSUS_FROM`, default
|
||||
/// 4,600 from 0, `IGNEUM_V5_CENSUS_THREADS` threads, default 88): per seed the class v4 sub-version 3 draw's
|
||||
/// attempt and its accepted program's minimum site ratio, the class v5 draw's attempt, and the counts under
|
||||
/// candidate floors, so the clean rejection rate and the attempts histogram of the chosen floor are on record.
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn v5_hot_census() {
|
||||
use crate::generator::V5_CLASS;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
let seeds: u32 = std::env::var("IGNEUM_V5_CENSUS_SEEDS").ok().and_then(|v| v.parse().ok()).unwrap_or(4600);
|
||||
let from: u32 = std::env::var("IGNEUM_V5_CENSUS_FROM").ok().and_then(|v| v.parse().ok()).unwrap_or(0);
|
||||
let threads: usize = std::env::var("IGNEUM_V5_CENSUS_THREADS").ok().and_then(|v| v.parse().ok()).unwrap_or(88);
|
||||
let next = AtomicU32::new(from);
|
||||
let t0 = std::time::Instant::now();
|
||||
// per seed: (k, v4 attempt, v4 min ratio, v4 min site, v5 attempt, v5 min ratio)
|
||||
let rows: Vec<(u32, u32, f64, usize, u32, f64)> = std::thread::scope(|sc| {
|
||||
let hs: Vec<_> = (0..threads).map(|_| sc.spawn(|| {
|
||||
let mut out = Vec::new();
|
||||
loop {
|
||||
let k = next.fetch_add(1, Ordering::Relaxed);
|
||||
if k >= from + seeds {
|
||||
break out;
|
||||
}
|
||||
let (epoch, era) = f8_seed(k);
|
||||
let v4 = f8_draw(&epoch, &era, V4_CLASS);
|
||||
let st = site_index_stats(&v4, ACCEPT_UNITS_DISTINCT_V4).unwrap();
|
||||
let (min4, site4) = distinct_ratio_on(&v4, &st, ACCEPT_UNITS_DISTINCT_V4, 0.0).unwrap();
|
||||
let v5 = f8_draw(&epoch, &era, V5_CLASS);
|
||||
let st5 = site_index_stats(&v5, ACCEPT_UNITS_DISTINCT_V4).unwrap();
|
||||
let (min5, _) = distinct_ratio_on(&v5, &st5, ACCEPT_UNITS_DISTINCT_V4, 0.0).unwrap();
|
||||
out.push((k, v4.attempt, min4, site4, v5.attempt, min5));
|
||||
}
|
||||
})).collect();
|
||||
let mut rows: Vec<_> = hs.into_iter().flat_map(|h| h.join().unwrap()).collect();
|
||||
rows.sort_by_key(|r| r.0);
|
||||
rows
|
||||
});
|
||||
let n = rows.len() as f64;
|
||||
let mut sorted: Vec<f64> = rows.iter().map(|r| r.2).collect();
|
||||
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||
let q = |p: f64| sorted[((p * (sorted.len() - 1) as f64).round() as usize).min(sorted.len() - 1)];
|
||||
println!("v5_hot_census: {} seeds {from}..{} in {:.0} s on {threads} threads", rows.len(), from + seeds, t0.elapsed().as_secs_f64());
|
||||
println!("class v4 sub-version 3 accepted programs' minimum site ratio: min {:.4} p0.1 {:.4} p1 {:.4} p5 {:.4} median {:.4} max {:.4}", sorted[0], q(0.001), q(0.01), q(0.05), q(0.5), sorted[sorted.len() - 1]);
|
||||
for floor in [0.98, 0.99, 0.995, 0.998, 0.999] {
|
||||
let under = rows.iter().filter(|r| r.2 < floor).count();
|
||||
println!("floor {floor:.3}: {under} of {} class v4 accepted programs under it ({:.3} percent)", rows.len(), under as f64 * 100.0 / n);
|
||||
}
|
||||
let mut lowest: Vec<_> = rows.iter().collect();
|
||||
lowest.sort_by(|a, b| a.2.partial_cmp(&b.2).unwrap());
|
||||
for r in lowest.iter().take(25) {
|
||||
println!("low: seed {} v4 attempt {} min site {} ratio {:.4}; v5 attempt {} min ratio {:.4}", r.0, r.1, r.3, r.2, r.4, r.5);
|
||||
}
|
||||
let hist = |pick: fn(&(u32, u32, f64, usize, u32, f64)) -> u32| {
|
||||
let mut h = std::collections::BTreeMap::new();
|
||||
for r in &rows {
|
||||
*h.entry(pick(r)).or_insert(0u32) += 1;
|
||||
}
|
||||
let mean = rows.iter().map(|r| pick(r) as f64).sum::<f64>() / n;
|
||||
(h, mean)
|
||||
};
|
||||
let (h4, m4) = hist(|r| r.1);
|
||||
let (h5, m5) = hist(|r| r.4);
|
||||
println!("attempts histogram class v4 (mean {m4:.3}): {:?}", h4);
|
||||
println!("attempts histogram class v5 (mean {m5:.3}): {:?}", h5);
|
||||
let moved = rows.iter().filter(|r| r.1 != r.4).count();
|
||||
println!("seeds whose class v5 draw lands on another attempt than the class v4 draw: {moved} of {} ({:.3} percent)", rows.len(), moved as f64 * 100.0 / n);
|
||||
let v5_under = rows.iter().filter(|r| r.5 < MIN_DISTINCT_RATIO_V5).count();
|
||||
println!("class v5 accepted programs under the class v5 floor {MIN_DISTINCT_RATIO_V5}: {v5_under} (must be 0)");
|
||||
assert_eq!(v5_under, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn distinct_bound_scales_with_the_load_count() {
|
||||
assert_eq!(min_distinct_sum(128), MIN_DISTINCT_SUM);
|
||||
|
|
|
|||
|
|
@ -2088,6 +2088,12 @@ mod tests {
|
|||
let first = candidate_class(seed, seed.as_bytes(), v4.attempt, v4.class);
|
||||
assert!(shadow_removable_count(&first.shadow) * 1000 > first.shadow.len() * SHADOW_REMOVABLE_MAX_PERMILLE || v5.attempt != v4.attempt, "{seed}: v5 differs from v4 without a redraw");
|
||||
}
|
||||
if v5.attempt != v4.attempt {
|
||||
// the attempt moved: the v4 draw's program under the v5 shape is refused by a class v5 rule ((c''') the
|
||||
// raised floor, or the acceptance reading a redrawn shadow), never silently
|
||||
let same = candidate_class(seed, seed.as_bytes(), v4.attempt, v5.class);
|
||||
assert!(crate::accept::check(&same).is_err(), "{seed}: the v5 draw skipped attempt {} without a reason", v4.attempt);
|
||||
}
|
||||
// the draw equality above is the claim; sub-version 3's own freshness rule (dataflow, with the last resort after
|
||||
// the 256 cap) is what the chain applies, so the sub-version 1 scan below is informational for v5
|
||||
let _ = scan(&v5);
|
||||
|
|
|
|||
Loading…
Reference in a new issue