dvcompute_experiment_cons 2.0.1

Discrete event simulation library (support of experiments for conservative distributed simulation)
Documentation
// Copyright (c) 2020-2022  David Sorokin <davsor@mail.ru>, based in Yoshkar-Ola, Russia
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use std::default::Default;
use std::collections::BTreeMap;
use std::sync::Mutex;
use std::io;
use std::io::*;
use std::result;

use dvcompute::simulation::event::*;
use dvcompute::simulation::observable::*;
use dvcompute::simulation::observable::observer::*;

use dvcompute_results::simulation::results::*;

use crate::simulation::experiment::*;
use crate::simulation::experiment::rendering::*;
use crate::simulation::utils::html::*;

/// Defines the `ExperimentView` that shows the last values
/// of simulation variables.
#[derive(Clone)]
pub struct LastValues {

    /// The title.
    pub title: String,

    /// The run title that may include special variables
    /// `$RUN_INDEX`, `$RUN_COUNT` and `$TITLE`.
    ///
    /// An example is "$TITLE / Run $RUN_INDEX of $RUN_COUNT".
    pub run_title: String,

    /// The description.
    pub description: Option<String>,

    /// It transforms data before they will be shown.
    pub format: Arc<dyn Fn(&str) -> String + Sync + Send>,

    /// The transform applied to the results before receiving the series.
    pub transform: Arc<dyn Fn() -> ResultTransform + Sync + Send>,

    /// It defines the series for which the last values are to be displayed.
    pub series: Arc<dyn Fn() -> ResultTransform + Sync + Send>
}

impl Default for LastValues {

    fn default() -> Self {
        Self {
            title: String::from("Last Values"),
            run_title: String::from("$TITLE / Run $RUN_INDEX of $RUN_COUNT"),
            description: Some(String::from("It shows the values in the final time point(s).")),
            format: Arc::new(|x| { String::from(x) }),
            transform: Arc::new(|| { ResultTransform::empty() }),
            series: Arc::new(|| { ResultTransform::empty() })
        }
    }
}

/// The state of the view.
#[derive(Clone)]
struct LastValueState {

    /// The view.
    view: Arc<Mutex<LastValues>>,

    /// The experiment.
    experiment: Arc<Mutex<Experiment>>,

    /// The last values.
    vals: Arc<Mutex<BTreeMap<usize, Vec<(String, String)>>>>
}

impl LastValueState {

    /// Create a new state.
    pub fn new(view: LastValues, experiment: Experiment) -> Self {
        let n = experiment.run_count;
        let mut m = BTreeMap::new();
        for i in 0 .. n {
            m.insert(i, Vec::new());
        }
        Self {
            view: Arc::new(Mutex::new(view)),
            experiment: Arc::new(Mutex::new(experiment)),
            vals: Arc::new(Mutex::new(m))
        }
    }

    /// Get the HTML code for a single run.
    pub fn write_html_single(&self, w: &mut dyn Write, index: usize) -> io::Result<()> {
        self.write_html_header(w, index)?;
        let view = self.view.lock().unwrap();
        let vals = self.vals.lock().unwrap();
        for (k, v) in vals[&0].iter() {
            write_html_pair(w, k, &(view.format)(v))?;
        }
        result::Result::Ok(())
    }

    /// Get the HTML code for multiple runs.
    pub fn write_html_multiple(&self, w: &mut dyn Write, index: usize) -> io::Result<()> {
        self.write_html_header(w, index)?;
        let view = self.view.lock().unwrap();
        let experiment = self.experiment.lock().unwrap();
        let vals = self.vals.lock().unwrap();
        let n = experiment.run_count;
        for run_index in 0 .. n {
            let subtitle = {
                view.run_title
                    .replace("$TITLE", &view.title)
                    .replace("$RUN_COUNT", &format!("{}", n))
                    .replace("$RUN_INDEX", &format!("{}", 1 + run_index))
            };
            write_html_header4(w, &subtitle)?;
            for (k, v) in vals[&run_index].iter() {
                write_html_pair(w, k, &(view.format)(v))?;
            }
        }
        result::Result::Ok(())
    }

    /// Write the HTML header.
    pub fn write_html_header(&self, w: &mut dyn Write, index: usize) -> io::Result<()> {
        let view = self.view.lock().unwrap();
        let id = format!("id{}", index);
        write_html_header3_by_id(w, &id, &view.title)?;
        if let Some(description) = &view.description {
            begin_html_paragraph(w)?;
            write_html_text(w, &description)?;
            end_html_paragraph(w)?;
        }
        result::Result::Ok(())
    }
}

impl<T> ExperimentReporter<WebPageRendering<T>> for LastValueState {

    fn initialise(&self) -> simulation::Result<()> {
        result::Result::Ok(())
    }

    fn finalise(&self) -> simulation::Result<()> {
        result::Result::Ok(())
    }

    fn simulate(&self, xs: &ExperimentData) -> CompositeBox<()> {
        let loc = {
            let experiment = self.experiment.lock().unwrap();
            experiment.locale.clone()
        };
        let exts = {
            let view = self.view.lock().unwrap();
            let transform = (view.transform)();
            let series = (view.series)();
            transform.and_then(&series)
                .call(&xs.results)
                .and_then(|rs| {
                    rs.get_string_vals()
                })
        };

        match exts {
            result::Result::Err(e) => {
                cons_event(move |_| {
                    let e = simulation::error::Error::panic(e);
                    result::Result::Err(e)
                })
                .into_composite()
                .into_boxed()
            },
            result::Result::Ok(exts) => {
                xs.predefined_observables
                    .in_stop_time()
                    .subscribe({
                        let st = self.clone();
                        cons_observer(move |_t, p| {
                            let loc = loc.clone();
                            let exts = exts.clone();
                            let st = st.clone();
                            enqueue_io_event(p.time,
                                cons_event(move |p| {
                                    let xys = event_sequence({
                                        let loc = loc.clone();
                                        exts.iter().map(move |ext| {
                                            let loc = loc.clone();
                                            let x = ext.id_path
                                                .iter()
                                                .map(move |id| { id.get_title(&loc) })
                                                .collect::<Vec<String>>()
                                                .join(".");
                                            (ext.data)()
                                                .map(move |y| { (x, y) })
                                        })
                                    }).call_event(p)?;

                                    let mut vals = st.vals.lock().unwrap();
                                    vals.insert(p.run.run_index, xys);

                                    result::Result::Ok(())
                                })
                                .into_boxed())
                            .call_event(p)
                        })
                    })
                    .into_composite()
                    .and_then(|h| {
                        disposable_composite(h)
                    })
                    .into_boxed()
            }
        }
    }

    fn context(&self) -> <WebPageRendering<T> as ExperimentRendering>::ExperimentContext {
        Box::new(self.clone())
    }
}

impl<T> ExperimentGenerator<WebPageRendering<T>> for LastValues {

    fn report(&self, _rendering: &WebPageRendering<T>, experiment: &Experiment, _env: &<WebPageRendering<T> as ExperimentRendering>::ExperimentEnvironment) -> Box<dyn ExperimentReporter<WebPageRendering<T>> + Sync + Send> {
        Box::new(LastValueState::new(self.clone(), experiment.clone()))
    }
}

impl<T> ExperimentView<WebPageRendering<T>> for LastValues {

    fn view(&self) -> Box<dyn ExperimentGenerator<WebPageRendering<T>>> {
        Box::new(self.clone())
    }
}

impl WebPageWriter for LastValueState {

    fn write_toc_html(&self, w: &mut dyn Write, index: usize) -> io::Result<()> {
        let view = self.view.lock().unwrap();
        let id = format!("#id{}", index);
        begin_html_list_item(w)?;
        write_html_link(w, &id, &view.title)?;
        end_html_list_item(w)
    }

    fn write_html(&self, w: &mut dyn Write, index: usize) -> io::Result<()> {
        let run_count = {
            let experiment = self.experiment.lock().unwrap();
            experiment.run_count
        };
        if run_count == 1 {
            self.write_html_single(w, index)
        } else {
            self.write_html_multiple(w, index)
        }
    }
}

/// Write the pair in HTML.
fn write_html_pair(w: &mut dyn Write, k: &str, v: &str) -> io::Result<()> {
    begin_html_paragraph(w)?;
    write_html_text(w, k)?;
    write_html_text(w, " = ")?;
    write_html_text(w, v)?;
    end_html_paragraph(w)
}