//! 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, 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 = 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(()) }