sciforge 0.0.3

A comprehensive scientific computing library in pure Rust with zero dependencies
Documentation
use super::engine::{BenchmarkMetrics, CSV_HEADER};
use crate::parser::html::{push_html_escaped, push_html_row};
use crate::parser::json::{JsonVal, write_json_object};
use crate::parser::markdown::{write_md_code_block, write_md_heading, write_md_row};
use crate::parser::toml::{push_toml_num, push_toml_section, push_toml_str};
use crate::parser::yaml::write_yaml_map;

pub struct BenchmarkReport {
    pub csv: String,
    pub markdown: String,
    pub html: String,
}

pub fn generate(metrics: &BenchmarkMetrics<'_>, result: &str) -> BenchmarkReport {
    let csv = generate_csv(metrics, result);
    let markdown = generate_markdown(metrics, result);
    let md_filename = format!("{}.md", sanitize_filename(metrics.experiment_name));
    let html = generate_html(metrics, result, &md_filename, &markdown);
    BenchmarkReport {
        csv,
        markdown,
        html,
    }
}

pub fn generate_csv(metrics: &BenchmarkMetrics<'_>, result: &str) -> String {
    format!("{}\n{},{}", CSV_HEADER, metrics.to_csv_row(), result)
}

fn generate_markdown(metrics: &BenchmarkMetrics<'_>, result: &str) -> String {
    let mut out = String::with_capacity(1024);
    out.push_str(&write_md_heading(
        1,
        &format!("Benchmark Report: {}", metrics.experiment_name),
    ));
    out.push_str(&write_md_heading(2, "Summary"));
    out.push_str("| Metric | Value |\n|---|---|\n");
    write_md_row(&mut out, "Experiment", metrics.experiment_name);
    write_md_row(&mut out, "Precision", metrics.precision);
    write_md_row(&mut out, "Elapsed (ms)", &metrics.elapsed_ms.to_string());
    write_md_row(&mut out, "Iterations", &metrics.iterations.to_string());
    write_md_row(
        &mut out,
        "Avg time (ns)",
        &format!("{:.2}", metrics.avg_time_ns),
    );
    write_md_row(
        &mut out,
        "Min time (ns)",
        &format!("{:.2}", metrics.min_time_ns),
    );
    write_md_row(
        &mut out,
        "Max time (ns)",
        &format!("{:.2}", metrics.max_time_ns),
    );
    write_md_row(&mut out, "Std dev", &format!("{:.4}", metrics.time_stddev));
    write_md_row(
        &mut out,
        "Iterations/sec",
        &format!("{:.2}", metrics.iterations_per_sec),
    );

    if metrics.total_flops > 0 {
        write_md_row(&mut out, "Total FLOPS", &metrics.total_flops.to_string());
    }
    if metrics.step_count > 0 {
        write_md_row(&mut out, "Steps", &metrics.step_count.to_string());
    }
    if metrics.eval_accuracy > 0.0 {
        write_md_row(
            &mut out,
            "Accuracy",
            &format!("{:.6}", metrics.eval_accuracy),
        );
    }
    if metrics.eval_error > 0.0 {
        write_md_row(&mut out, "Error", &format!("{:.6}", metrics.eval_error));
    }
    if metrics.eval_r_squared > 0.0 {
        write_md_row(&mut out, "R²", &format!("{:.6}", metrics.eval_r_squared));
    }

    out.push_str(&write_md_heading(2, "Result"));
    out.push_str(&write_md_code_block(result));

    out
}

fn generate_html(
    metrics: &BenchmarkMetrics<'_>,
    result: &str,
    md_filename: &str,
    md_content: &str,
) -> String {
    let json = generate_json(metrics, result);
    let yaml = generate_yaml(metrics, result);
    let csv_data = generate_csv(metrics, result);
    let toml_data = generate_toml(metrics, result);

    let mut h = String::with_capacity(4096);
    h.push_str(
        "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>Benchmark: ",
    );
    push_html_escaped(&mut h, metrics.experiment_name);
    h.push_str("</title>\n<style>\n");
    h.push_str(
        "body{font-family:system-ui,sans-serif;margin:2em;background:#0d1117;color:#c9d1d9}",
    );
    h.push_str(
        "h1{color:#58a6ff}h2{color:#79c0ff;border-bottom:1px solid #21262d;padding-bottom:.3em}",
    );
    h.push_str("table{border-collapse:collapse;margin:1em 0}th,td{border:1px solid #30363d;padding:.4em .8em;text-align:left}");
    h.push_str("th{background:#161b22}tr:nth-child(even){background:#161b22}");
    h.push_str("pre{background:#161b22;padding:1em;border-radius:6px;overflow-x:auto}");
    h.push_str("code{font-family:ui-monospace,monospace;font-size:.9em}");
    h.push_str("a{color:#58a6ff}.tabs{display:flex;gap:0;margin-top:1em}");
    h.push_str(".tab{padding:.5em 1em;cursor:pointer;background:#161b22;border:1px solid #30363d;border-bottom:none;border-radius:6px 6px 0 0;color:#8b949e}");
    h.push_str(".tab.active{background:#0d1117;color:#c9d1d9;border-bottom:1px solid #0d1117}");
    h.push_str(
        ".panel{display:none;border:1px solid #30363d;padding:1em;border-radius:0 6px 6px 6px}",
    );
    h.push_str(".panel.active{display:block}");
    h.push_str("</style>\n</head>\n<body>\n");

    h.push_str("<h1>Benchmark: ");
    push_html_escaped(&mut h, metrics.experiment_name);
    h.push_str("</h1>\n");

    h.push_str("<h2>Summary</h2>\n<table>\n<tr><th>Metric</th><th>Value</th></tr>\n");
    push_html_row(&mut h, "Experiment", metrics.experiment_name);
    push_html_row(&mut h, "Precision", metrics.precision);
    push_html_row(&mut h, "Elapsed (ms)", &metrics.elapsed_ms.to_string());
    push_html_row(&mut h, "Iterations", &metrics.iterations.to_string());
    push_html_row(
        &mut h,
        "Avg time (ns)",
        &format!("{:.2}", metrics.avg_time_ns),
    );
    push_html_row(
        &mut h,
        "Min time (ns)",
        &format!("{:.2}", metrics.min_time_ns),
    );
    push_html_row(
        &mut h,
        "Max time (ns)",
        &format!("{:.2}", metrics.max_time_ns),
    );
    push_html_row(&mut h, "Std dev", &format!("{:.4}", metrics.time_stddev));
    push_html_row(
        &mut h,
        "Iterations/sec",
        &format!("{:.2}", metrics.iterations_per_sec),
    );
    h.push_str("</table>\n");

    h.push_str("\n<h2>Result</h2>\n<pre><code>");
    push_html_escaped(&mut h, result);
    h.push_str("</code></pre>\n");

    h.push_str("\n<h2>Full Report</h2>\n<p><a href=\"");
    push_html_escaped(&mut h, md_filename);
    h.push_str("\">");
    push_html_escaped(&mut h, md_filename);
    h.push_str("</a></p>\n");

    h.push_str("\n<h2>Data</h2>\n");
    h.push_str("<div class=\"tabs\">\n");
    h.push_str("<div class=\"tab active\" onclick=\"switchTab(event,'csv')\">CSV</div>\n");
    h.push_str("<div class=\"tab\" onclick=\"switchTab(event,'json')\">JSON</div>\n");
    h.push_str("<div class=\"tab\" onclick=\"switchTab(event,'yaml')\">YAML</div>\n");
    h.push_str("<div class=\"tab\" onclick=\"switchTab(event,'toml')\">TOML</div>\n");
    h.push_str("</div>\n");

    h.push_str("<div id=\"csv\" class=\"panel active\"><pre><code>");
    push_html_escaped(&mut h, &csv_data);
    h.push_str("</code></pre></div>\n");

    h.push_str("<div id=\"json\" class=\"panel\"><pre><code>");
    push_html_escaped(&mut h, &json);
    h.push_str("</code></pre></div>\n");

    h.push_str("<div id=\"yaml\" class=\"panel\"><pre><code>");
    push_html_escaped(&mut h, &yaml);
    h.push_str("</code></pre></div>\n");

    h.push_str("<div id=\"toml\" class=\"panel\"><pre><code>");
    push_html_escaped(&mut h, &toml_data);
    h.push_str("</code></pre></div>\n");

    h.push_str("\n<h2>Markdown Preview</h2>\n<pre><code>");
    push_html_escaped(&mut h, md_content);
    h.push_str("</code></pre>\n");

    h.push_str("\n<script>\nfunction switchTab(e,id){");
    h.push_str("document.querySelectorAll('.tab').forEach(t=>t.classList.remove('active'));");
    h.push_str("document.querySelectorAll('.panel').forEach(p=>p.classList.remove('active'));");
    h.push_str(
        "e.target.classList.add('active');document.getElementById(id).classList.add('active');",
    );
    h.push_str("}\n</script>\n</body>\n</html>\n");

    h
}

pub fn generate_json(metrics: &BenchmarkMetrics<'_>, result: &str) -> String {
    generate_json_tagged(metrics, result, &[])
}

pub fn generate_json_tagged(
    metrics: &BenchmarkMetrics<'_>,
    result: &str,
    tags: &[(&str, &str)],
) -> String {
    let elapsed = metrics.elapsed_ms.to_string();
    let iterations = metrics.iterations.to_string();
    let avg = format!("{:.2}", metrics.avg_time_ns);
    let min = format!("{:.2}", metrics.min_time_ns);
    let max = format!("{:.2}", metrics.max_time_ns);
    let stddev = format!("{:.4}", metrics.time_stddev);
    let ips = format!("{:.2}", metrics.iterations_per_sec);

    let mut fields: Vec<(&str, JsonVal<'_>)> = Vec::with_capacity(tags.len() + 10);
    for (k, v) in tags {
        fields.push((k, JsonVal::Str(v)));
    }
    fields.push(("experiment_name", JsonVal::Str(metrics.experiment_name)));
    fields.push(("precision", JsonVal::Str(metrics.precision)));
    fields.push(("elapsed_ms", JsonVal::Raw(&elapsed)));
    fields.push(("iterations", JsonVal::Raw(&iterations)));
    fields.push(("avg_time_ns", JsonVal::Raw(&avg)));
    fields.push(("min_time_ns", JsonVal::Raw(&min)));
    fields.push(("max_time_ns", JsonVal::Raw(&max)));
    fields.push(("time_stddev", JsonVal::Raw(&stddev)));
    fields.push(("iterations_per_sec", JsonVal::Raw(&ips)));
    fields.push(("result", JsonVal::Str(result)));
    write_json_object(&fields)
}

pub fn generate_yaml(metrics: &BenchmarkMetrics<'_>, result: &str) -> String {
    generate_yaml_tagged(metrics, result, &[])
}

pub fn generate_yaml_tagged(
    metrics: &BenchmarkMetrics<'_>,
    result: &str,
    tags: &[(&str, &str)],
) -> String {
    let elapsed = metrics.elapsed_ms.to_string();
    let iterations = metrics.iterations.to_string();
    let avg = format!("{:.2}", metrics.avg_time_ns);
    let min = format!("{:.2}", metrics.min_time_ns);
    let max = format!("{:.2}", metrics.max_time_ns);
    let stddev = format!("{:.4}", metrics.time_stddev);
    let ips = format!("{:.2}", metrics.iterations_per_sec);

    let mut fields: Vec<(&str, &str)> = Vec::with_capacity(tags.len() + 10);
    for (k, v) in tags {
        fields.push((k, v));
    }
    fields.push(("experiment_name", metrics.experiment_name));
    fields.push(("precision", metrics.precision));
    fields.push(("elapsed_ms", &elapsed));
    fields.push(("iterations", &iterations));
    fields.push(("avg_time_ns", &avg));
    fields.push(("min_time_ns", &min));
    fields.push(("max_time_ns", &max));
    fields.push(("time_stddev", &stddev));
    fields.push(("iterations_per_sec", &ips));
    fields.push(("result", result));
    write_yaml_map(&fields)
}

pub fn generate_toml(metrics: &BenchmarkMetrics<'_>, result: &str) -> String {
    generate_toml_tagged(metrics, result, &[])
}

pub fn generate_toml_tagged(
    metrics: &BenchmarkMetrics<'_>,
    result: &str,
    tags: &[(&str, &str)],
) -> String {
    let mut t = String::with_capacity(512);
    push_toml_section(&mut t, "benchmark");
    for (k, v) in tags {
        push_toml_str(&mut t, k, v);
    }
    push_toml_str(&mut t, "experiment_name", metrics.experiment_name);
    push_toml_str(&mut t, "precision", metrics.precision);
    push_toml_num(&mut t, "elapsed_ms", &metrics.elapsed_ms.to_string());
    push_toml_num(&mut t, "iterations", &metrics.iterations.to_string());
    push_toml_num(
        &mut t,
        "avg_time_ns",
        &format!("{:.2}", metrics.avg_time_ns),
    );
    push_toml_num(
        &mut t,
        "min_time_ns",
        &format!("{:.2}", metrics.min_time_ns),
    );
    push_toml_num(
        &mut t,
        "max_time_ns",
        &format!("{:.2}", metrics.max_time_ns),
    );
    push_toml_num(
        &mut t,
        "time_stddev",
        &format!("{:.4}", metrics.time_stddev),
    );
    push_toml_num(
        &mut t,
        "iterations_per_sec",
        &format!("{:.2}", metrics.iterations_per_sec),
    );
    push_toml_str(&mut t, "result", result);
    t
}

pub fn sanitize_filename(name: &str) -> String {
    name.chars()
        .map(|c| match c {
            'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' | '.' => c,
            '/' | ':' | ' ' => '_',
            _ => '_',
        })
        .collect()
}