mod data;
mod draw;
mod options;
mod utils;
pub use options::PlotOptions;
use plotters::coord;
use plotters::drawing::{DrawingArea, DrawingAreaErrorKind, IntoDrawingArea};
use plotters::prelude::{BitMapBackend, DrawingBackend};
use plotters::style::colors;
use std::error::Error;
use std::fmt::{self, Debug};
use std::fs::DirBuilder;
use std::io;
use std::path::Path;
use crate::history::History;
use crate::state::State;
use crate::Mode;
use data::PlotData;
pub type Backend<'a> = BitMapBackend<'a>;
pub type DrawingError<'a> = DrawingAreaErrorKind<<Backend<'a> as DrawingBackend>::ErrorType>;
pub const PLOT_HEIGHT: u32 = 1200;
pub const PLOT_WIDTH: u32 = 1200;
#[derive(Clone, Debug)]
pub struct Plot {
data: PlotData,
options: PlotOptions,
mode: Mode,
last_data_point_evals: Option<usize>,
last_data_point_generation: Option<usize>,
}
impl Plot {
pub(crate) fn new(dimensions: usize, options: PlotOptions, mode: Mode) -> Self {
Self {
data: PlotData::new(dimensions),
options,
mode,
last_data_point_evals: None,
last_data_point_generation: None,
}
}
pub(crate) fn get_next_data_point_evals(&self) -> usize {
match self.last_data_point_evals {
Some(evals) => evals + self.options.min_gap_evals,
None => 0,
}
}
pub(crate) fn add_data_point(
&mut self,
current_function_evals: usize,
state: &State,
history: &History,
) {
let already_added = match self.last_data_point_generation {
Some(generation) => generation == state.generation(),
None => false,
};
if !already_added {
self.data
.add_data_point(current_function_evals, state, history);
self.last_data_point_evals = Some(current_function_evals);
self.last_data_point_generation = Some(state.generation());
}
}
pub fn save_to_file<P: AsRef<Path>>(
&self,
path: P,
create_dirs: bool,
) -> Result<(), PlotError> {
let path = path.as_ref();
if create_dirs {
if let Some(parent) = path.parent() {
DirBuilder::new().recursive(true).create(parent)?;
}
}
let plot = self.build_plot(&path)?;
plot.present().map_err(Into::into)
}
fn build_plot<'a, P: AsRef<Path> + 'a>(
&self,
path: &'a P,
) -> Result<DrawingArea<Backend<'a>, coord::Shift>, DrawingError> {
let root_area = Backend::new(path, (PLOT_WIDTH, PLOT_HEIGHT)).into_drawing_area();
root_area.fill(&colors::WHITE)?;
let mut child_drawing_areas = root_area.split_evenly((2, 2)).into_iter();
let top_left = child_drawing_areas.next().unwrap();
let top_right = child_drawing_areas.next().unwrap();
let bottom_left = child_drawing_areas.next().unwrap();
let bottom_right = child_drawing_areas.next().unwrap();
draw::draw_single_dimensioned(self.mode, &self.data, &top_left)?;
draw::draw_mean(&self.data, &self.options, &top_right)?;
draw::draw_sqrt_eigenvalues(&self.data, &bottom_left)?;
draw::draw_coord_axis_scales(&self.data, &bottom_right)?;
Ok(root_area)
}
pub fn len(&self) -> usize {
self.data.len()
}
pub fn capacity(&self) -> usize {
self.data.capacity()
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
pub fn clear(&mut self) {
self.data.clear();
}
}
#[derive(Debug)]
pub enum PlotError<'a> {
DrawingError(DrawingError<'a>),
IoError(io::Error),
}
impl<'a> fmt::Display for PlotError<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match *self {
PlotError::DrawingError(ref e) => write!(fmt, "DrawingError({})", e),
PlotError::IoError(ref e) => write!(fmt, "IoError({})", e),
}
}
}
impl<'a> Error for PlotError<'a> {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match *self {
PlotError::DrawingError(ref e) => Some(e),
PlotError::IoError(ref e) => Some(e),
}
}
}
impl<'a> From<DrawingError<'a>> for PlotError<'a> {
fn from(error: DrawingError<'a>) -> Self {
PlotError::DrawingError(error)
}
}
impl<'a> From<io::Error> for PlotError<'a> {
fn from(error: io::Error) -> Self {
PlotError::IoError(error)
}
}
#[cfg(test)]
mod tests {
use nalgebra::DVector;
use super::*;
use crate::CMAESOptions;
fn get_plot_path(name: &str) -> String {
format!("{}/test_output/{}.png", env!("CARGO_MANIFEST_DIR"), name)
}
#[test]
fn test_plot_not_enabled() {
let state = CMAESOptions::new(vec![1.0; 10], 1.0)
.build(|_: &DVector<f64>| 0.0)
.unwrap();
assert!(state.get_plot().is_none());
}
#[test]
fn test_plot_empty() {
let state = CMAESOptions::new(vec![1.0; 10], 1.0)
.enable_plot(PlotOptions::new(0, false))
.build(|_: &DVector<f64>| 0.0)
.unwrap();
let plot = state.get_plot().unwrap();
assert!(plot
.save_to_file(get_plot_path("test_plot_empty"), true)
.is_ok())
}
#[test]
fn test_plot() {
let mut state = CMAESOptions::new(vec![1.0; 10], 1.0)
.enable_plot(PlotOptions::new(0, false))
.build(|_: &DVector<f64>| 0.0)
.unwrap();
for _ in 0..10 {
let _ = state.next();
}
let plot = state.get_plot().unwrap();
assert!(plot.save_to_file(get_plot_path("test_plot"), true).is_ok());
}
#[test]
fn test_redundant_plot() {
let mut state = CMAESOptions::new(vec![1.0; 10], 1.0)
.enable_plot(PlotOptions::new(0, false))
.build(|_: &DVector<f64>| 0.0)
.unwrap();
for _ in 0..10 {
let _ = state.add_plot_point();
}
assert_eq!(state.get_plot().unwrap().len(), 1);
}
#[test]
fn test_plot_clear() {
let mut state = CMAESOptions::new(vec![0.0; 10], 1.0)
.enable_plot(PlotOptions::new(0, false))
.build(|_: &DVector<f64>| 0.0)
.unwrap();
assert_eq!(state.get_plot().unwrap().len(), 1);
assert_eq!(state.get_plot().unwrap().capacity(), 4);
state.get_mut_plot().unwrap().clear();
assert_eq!(state.get_plot().unwrap().len(), 1);
assert_eq!(state.get_plot().unwrap().capacity(), 4);
for _ in 0..10 {
let _ = state.next();
}
assert_eq!(state.get_plot().unwrap().len(), 11);
assert_eq!(state.get_plot().unwrap().capacity(), 16);
state.get_mut_plot().unwrap().clear();
assert_eq!(state.get_plot().unwrap().len(), 1);
assert_eq!(state.get_plot().unwrap().capacity(), 16);
let plot = state.get_plot().unwrap();
assert!(plot
.save_to_file(get_plot_path("test_plot_clear"), true)
.is_ok());
}
}