Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Plotting

Native plotting is optional in alchemrs.

The crate exposes SVG rendering helpers behind the plotting Cargo feature:

cargo add alchemrs --features plotting

or for this repository:

cargo test --features plotting

Current scope

The first plotting surface is intentionally small:

  • render_convergence_svg
  • render_time_convergence_svg
  • render_overlap_matrix_svg
  • render_delta_f_state_svg
  • render_ti_dhdl_svg
  • render_block_average_svg

These consume plot-ready analysis values and return SVG strings:

  • render_convergence_svg for ConvergencePoint
  • render_time_convergence_svg for TimeConvergencePoint
  • render_overlap_matrix_svg for OverlapMatrix
  • render_delta_f_state_svg for DeltaFMatrix
  • render_ti_dhdl_svg for DhdlSeries
  • render_block_average_svg for BlockEstimate

Example figures

The docs include checked-in SVGs generated with:

cargo run --example generate_doc_plots --features plotting

Convergence trace:

MBAR convergence example

Overlap heatmap:

MBAR overlap example

Adjacent-state ΔF plot:

Adjacent-state ΔF example

TI dH/dlambda plot:

TI dHdl example

Block-average plot:

Block average example

Example

use alchemrs::{
    ConvergencePlotOptions, MbarOptions, extract_u_nk, mbar_convergence, render_convergence_svg,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
let windows = vec![
    extract_u_nk("lambda0.out", 300.0)?,
    extract_u_nk("lambda1.out", 300.0)?,
];
let points = mbar_convergence(&windows, Some(MbarOptions::default()))?;
let svg = render_convergence_svg(
    &points,
    Some(ConvergencePlotOptions {
        title: "MBAR Convergence".to_string(),
        ..ConvergencePlotOptions::default()
    }),
)?;
assert!(svg.contains("<svg"));
Ok(())
}

Time-convergence plots use shared per-window elapsed simulation time rather than the number of lambda windows included:

use alchemrs::{
    MbarOptions, TimeConvergencePlotOptions, extract_u_nk, mbar_time_convergence,
    render_time_convergence_svg,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
let windows = vec![
    extract_u_nk("lambda0.out", 300.0)?,
    extract_u_nk("lambda1.out", 300.0)?,
];
let points = mbar_time_convergence(&windows, Some(MbarOptions::default()), None)?;
let svg = render_time_convergence_svg(
    &points,
    Some(TimeConvergencePlotOptions {
        title: "MBAR Time Convergence".to_string(),
        ..TimeConvergencePlotOptions::default()
    }),
)?;
assert!(svg.contains("<svg"));
Ok(())
}

Overlap heatmaps can be rendered directly from the diagnostics layer:

use alchemrs::{
    MbarEstimator, MbarOptions, OverlapPlotOptions, extract_u_nk, render_overlap_matrix_svg,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
let windows = vec![
    extract_u_nk("lambda0.out", 300.0)?,
    extract_u_nk("lambda1.out", 300.0)?,
];
let fit = MbarEstimator::new(MbarOptions::default()).fit(&windows)?;
let overlap = fit.overlap_matrix()?;
let svg = render_overlap_matrix_svg(
    &overlap,
    Some(OverlapPlotOptions {
        title: "MBAR Overlap".to_string(),
        ..OverlapPlotOptions::default()
    }),
)?;
assert!(svg.contains("<svg"));
Ok(())
}

Adjacent-state ΔF plots can be rendered from estimator results:

use alchemrs::{
    DeltaFStatePlotOptions, MbarEstimator, MbarOptions, extract_u_nk, render_delta_f_state_svg,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
let windows = vec![
    extract_u_nk("lambda0.out", 300.0)?,
    extract_u_nk("lambda1.out", 300.0)?,
];
let fit = MbarEstimator::new(MbarOptions::default()).fit(&windows)?;
let delta_f = fit.result_with_uncertainty()?;
let svg = render_delta_f_state_svg(
    &delta_f,
    Some(DeltaFStatePlotOptions {
        title: "Adjacent ΔF".to_string(),
        ..DeltaFStatePlotOptions::default()
    }),
)?;
assert!(svg.contains("<svg"));
Ok(())
}

TI dH/dlambda plots can be rendered directly from parsed TI windows:

use alchemrs::{TiDhdlPlotOptions, extract_dhdl, render_ti_dhdl_svg};

fn main() -> Result<(), Box<dyn std::error::Error>> {
let series = vec![
    extract_dhdl("lambda0.out", 300.0)?,
    extract_dhdl("lambda1.out", 300.0)?,
];
let svg = render_ti_dhdl_svg(
    &series,
    Some(TiDhdlPlotOptions {
        title: "TI dH/dlambda".to_string(),
        ..TiDhdlPlotOptions::default()
    }),
)?;
assert!(svg.contains("<svg"));
Ok(())
}

Block-average plots can be rendered from library block analysis results:

use alchemrs::{
    BlockAveragePlotOptions, MbarEstimator, MbarOptions, extract_u_nk,
    render_block_average_svg,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
let windows = vec![
    extract_u_nk("lambda0.out", 300.0)?,
    extract_u_nk("lambda1.out", 300.0)?,
];
let blocks = MbarEstimator::new(MbarOptions::default()).block_average(&windows, 4)?;
let svg = render_block_average_svg(
    &blocks,
    Some(BlockAveragePlotOptions {
        title: "MBAR Block Average".to_string(),
        ..BlockAveragePlotOptions::default()
    }),
)?;
assert!(svg.contains("<svg"));
Ok(())
}

Why it is feature-gated

Plotting is presentation logic, not core analysis logic. Making it optional keeps:

  • the default dependency footprint smaller
  • the analysis and estimator surface independent from graphics code
  • library usage lightweight for users who only want parsing and estimation