Spaces:
Sleeping
Sleeping
File size: 5,495 Bytes
3afc977 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | //! Generate → execute → filter → record. The data engine's main loop. Each kept
//! program is independently reproducible from (master seed, per-program seed).
use std::collections::hash_map::DefaultHasher;
use std::collections::HashSet;
use std::fs::{self, File};
use std::hash::{Hash, Hasher};
use std::io::{BufWriter, Write};
use std::path::Path;
use anyhow::{Context, Result};
use rand::{RngCore, SeedableRng};
use rand_chacha::ChaCha20Rng;
use crate::grammar::{GenConfig, Generator};
use crate::mutate;
use crate::sandbox::Sandbox;
use crate::spec::{self, Features, Record, RejectStats};
use crate::verify::{self, Verdict};
/// Mutation-gate parameters.
const N_PROBES: usize = 24;
const MAX_ADDED_TESTS: usize = 8;
pub struct GenParams {
pub count: usize,
pub seed: u64,
pub fixed_difficulty: Option<u8>,
pub n_examples: usize,
pub n_tests: usize,
pub budget: i64,
pub out: String,
/// Run the mutation gate (strengthen tests). Off for fast bulk training data.
pub gate: bool,
}
pub fn run(p: GenParams) -> Result<()> {
if let Some(parent) = Path::new(&p.out).parent() {
if !parent.as_os_str().is_empty() {
fs::create_dir_all(parent).ok();
}
}
let file = File::create(&p.out).with_context(|| format!("creating {}", p.out))?;
let mut w = BufWriter::new(file);
let sb = Sandbox::new().map_err(|e| anyhow::anyhow!("init sandbox: {e}"))?;
let mut master = ChaCha20Rng::seed_from_u64(p.seed);
let mut stats = RejectStats::default();
let mut kept = 0usize;
let mut attempts = 0usize;
let mut self_verify_fail = 0usize;
let mut seen: HashSet<u64> = HashSet::new();
let mut total_added_tests = 0usize;
let mut total_mutants = 0usize;
let mut total_distinct = 0usize;
let max_attempts = p.count.saturating_mul(50).max(1000);
while kept < p.count && attempts < max_attempts {
attempts += 1;
let diff = p
.fixed_difficulty
.unwrap_or(((attempts - 1) % 5) as u8);
let prog_seed = master.next_u64();
let mut prng = ChaCha20Rng::seed_from_u64(prog_seed);
let cfg = GenConfig::for_difficulty(diff);
let prog = Generator::new(&mut prng, cfg).generate();
// Uniqueness: never keep the same source twice. Critical for the
// data-efficiency study, which controls how many UNIQUE programs exist.
let mut hasher = DefaultHasher::new();
prog.source.hash(&mut hasher);
if !seen.insert(hasher.finish()) {
stats.duplicate += 1;
continue;
}
let extracted = spec::extract(
&sb, &mut prng, &prog, p.n_examples, p.n_tests, p.budget, &mut stats,
);
let (examples, tests) = match extracted {
Some(x) => x,
None => continue,
};
// Invariant: the reference program must satisfy its own held-out tests.
if verify::verify(&sb, &prog.source, &tests, p.budget) != Verdict::Pass {
self_verify_fail += 1;
continue;
}
// Mutation gate: strengthen tests until they catch every behaviorally
// distinct mutant. Reject if that can't be done within the cap. Skipped
// for bulk training data (--no-gate), where test strength doesn't matter.
let tests = if p.gate {
let probes = spec::sample_inputs(&prog.params, N_PROBES, &mut prng);
match mutate::strengthen(&sb, &prog.source, tests, &probes, p.budget, MAX_ADDED_TESTS) {
Some(g) => {
total_added_tests += g.added;
total_mutants += g.mutants_total;
total_distinct += g.mutants_distinct;
g.tests
}
None => {
stats.weak_tests += 1;
continue;
}
}
} else {
tests
};
let infill = spec::make_infill(&prog.source, &mut prng);
let params = prog.params.iter().map(|t| spec::type_name(*t).to_string()).collect();
let output = examples.first().map(|e| e.output.clone()).unwrap_or(crate::value::LValue::Nil);
let features = Features::build(&prog.source, &prog.features, &output);
let rec = Record {
id: format!("echo1-{:08}", kept),
difficulty: diff,
seed: prog_seed,
source: prog.source,
params,
examples,
tests,
infill,
features,
};
let line = serde_json::to_string(&rec)?;
w.write_all(line.as_bytes())?;
w.write_all(b"\n")?;
kept += 1;
}
w.flush()?;
eprintln!("kept {kept} / requested {}", p.count);
eprintln!("attempts {attempts}");
eprintln!("rejects load_fail={} run_fail={} nonobservable={} nondet={} noop/sparse={} dup={} weak_tests={}",
stats.load_fail, stats.run_fail, stats.nonobservable, stats.nondeterministic, stats.noop, stats.duplicate, stats.weak_tests);
eprintln!("self_verify_fail {self_verify_fail} (should be 0)");
eprintln!("mutation gate: {total_mutants} mutants, {total_distinct} behaviorally distinct, {total_added_tests} discriminating tests added across {kept} kept programs");
eprintln!("wrote {}", p.out);
if kept < p.count {
eprintln!("WARN: hit attempt cap before reaching requested count");
}
Ok(())
}
|