Skip to main content

replay_features/
replay_features.rs

1//! Replay feature detection/scoring on cached hills with one or more configs.
2//! This avoids repeating raw-file parsing and hill detection during experiments.
3use std::path::PathBuf;
4use std::time::Instant;
5
6use anyhow::Context;
7use clap::Parser;
8use koth_ms::{config::KothConfig, input::read_hills, output::write_features_parquet};
9
10#[derive(Parser)]
11struct Args {
12    hills: PathBuf,
13    #[arg(long, required = true)]
14    config: Vec<PathBuf>,
15    #[arg(long)]
16    output: PathBuf,
17    #[arg(long, default_value_t = 8)]
18    threads: usize,
19}
20
21fn main() -> anyhow::Result<()> {
22    let args = Args::parse();
23    rayon::ThreadPoolBuilder::new()
24        .num_threads(args.threads)
25        .build_global()?;
26    let start = Instant::now();
27    let hills = read_hills(&args.hills).context("read cached hills")?;
28    let read_s = start.elapsed().as_secs_f64();
29    for config_path in &args.config {
30        let label = config_path
31            .file_stem()
32            .context("config needs a file stem")?;
33        let out = args.output.join(label);
34        anyhow::ensure!(!out.exists(), "refusing to overwrite {}", out.display());
35        let config = KothConfig::from_toml(config_path)?;
36        let start = Instant::now();
37        let features = koth_ms::run_features(&hills, &config.features, &config.file)?;
38        let feature_s = start.elapsed().as_secs_f64();
39        let start = Instant::now();
40        let scored = koth_ms::run_scoring(
41            &features,
42            &config.scoring,
43            &config.features,
44            config.file.polarity,
45        );
46        let scoring_s = start.elapsed().as_secs_f64();
47        std::fs::create_dir_all(&out)?;
48        let start = Instant::now();
49        write_features_parquet(&scored, &out.join("features.parquet"), config.file.polarity)?;
50        let write_s = start.elapsed().as_secs_f64();
51        std::fs::write(out.join("config.toml"), config.to_toml_string()?)?;
52        let report = serde_json::json!({
53            "hills": hills.len(), "features_before_filter": features.len(),
54            "features_after_filter": scored.len(), "read_hills_s": read_s,
55            "feature_s": feature_s, "scoring_s": scoring_s, "write_s": write_s,
56            "input": args.hills, "config": config_path, "threads": args.threads,
57        });
58        std::fs::write(
59            out.join("timing.json"),
60            serde_json::to_string_pretty(&report)?,
61        )?;
62        println!(
63            "{}: {} features, detection {:.2}s, scoring {:.2}s, writing {:.2}s",
64            label.to_string_lossy(),
65            scored.len(),
66            feature_s,
67            scoring_s,
68            write_s
69        );
70    }
71    Ok(())
72}