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_svgrender_time_convergence_svgrender_overlap_matrix_svgrender_delta_f_state_svgrender_ti_dhdl_svgrender_block_average_svg
These consume plot-ready analysis values and return SVG strings:
render_convergence_svgforConvergencePointrender_time_convergence_svgforTimeConvergencePointrender_overlap_matrix_svgforOverlapMatrixrender_delta_f_state_svgforDeltaFMatrixrender_ti_dhdl_svgforDhdlSeriesrender_block_average_svgforBlockEstimate
Example figures
The docs include checked-in SVGs generated with:
cargo run --example generate_doc_plots --features plotting
Convergence trace:
Overlap heatmap:
Adjacent-state ΔF plot:
TI dH/dlambda plot:
Block-average plot:
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