#![doc(
html_logo_url = "https://raw.githubusercontent.com/krABMaga/krABMaga.github.io/main/static/images/krabmaga_docs.png"
)]
pub mod engine;
#[doc(hidden)]
pub mod explore;
#[doc(hidden)]
pub mod utils;
#[doc(hidden)]
pub use {
::lazy_static::*,
cfg_if, chrono,
core::fmt,
csv::{Reader, Writer},
hashbrown,
indicatif::ProgressBar,
rand, rand_pcg, rayon,
rayon::prelude::*,
std::collections::HashMap,
std::error::Error,
std::fs,
std::fs::File,
std::fs::OpenOptions,
std::io,
std::io::prelude::*,
std::io::Write,
std::process::{Command, Stdio},
std::sync::{Arc, Mutex},
std::thread,
std::time::Duration,
std::time::Instant,
};
#[cfg(any(feature = "visualization", feature = "visualization_wasm",))]
pub mod visualization;
#[cfg(any(feature = "visualization", feature = "visualization_wasm",))]
pub use bevy;
#[doc(hidden)]
pub use rand::{
distr::StandardUniform,
rng,
Rng,
};
#[doc(hidden)]
#[cfg(not(feature = "visualization_wasm"))]
pub use {
crate::utils::monitoring::ui::UI,
crossterm,
crossterm::event::poll,
crossterm::{
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
},
plotters,
sysinfo::*,
tui::{
backend::{Backend, CrosstermBackend},
Terminal,
},
};
#[cfg(feature = "distributed_mpi")]
pub extern crate mpi;
#[cfg(feature = "distributed_mpi")]
pub use {
memoffset::{offset_of, span_of},
mpi::datatype::DynBufferMut,
mpi::datatype::PartitionMut,
mpi::environment::Universe,
mpi::point_to_point as p2p,
mpi::traits::Equivalence,
mpi::Count,
mpi::{datatype::UserDatatype, traits::*, Address},
};
#[cfg(feature = "distributed_mpi")]
lazy_static! {
pub static ref UNIVERSE: Universe =
mpi::initialize().expect("Error initialing mpi environment");
}
#[doc(hidden)]
#[cfg(feature = "bayesian")]
pub use {friedrich, statrs};
#[doc(hidden)]
#[cfg(feature = "aws")]
pub use {
aws_config,
aws_sdk_lambda,
aws_sdk_sqs,
futures::executor::block_on,
lambda_runtime,
serde_json,
serde_json::{json, Value},
std::io::BufReader,
tokio,
tokio::runtime::Runtime, };
pub enum ComputingMode {
Parallel,
Distributed,
Cloud,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub enum Info {
Verbose,
Normal,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum ExploreMode {
Exaustive,
Matched,
}
#[doc(hidden)]
#[derive(Clone)]
pub struct PlotData {
pub name: String,
pub series: HashMap<String, Vec<(f64, f64)>>,
pub min_x: f64,
pub max_x: f64,
pub min_y: f64,
pub max_y: f64,
pub xlabel: String,
pub ylabel: String,
pub to_be_stored: bool,
}
#[doc(hidden)]
impl PlotData {
pub fn new(name: String, xlabel: String, ylabel: String, to_be_stored: bool) -> PlotData {
PlotData {
name,
series: HashMap::new(),
min_x: f64::MAX,
max_x: f64::MIN,
min_y: f64::MAX,
max_y: f64::MIN,
xlabel,
ylabel,
to_be_stored,
}
}
#[cfg(not(feature = "visualization_wasm"))]
pub fn store_plot(&self, rep: u64) {
let n_markers = 3;
let colors = [
RED,
RGBColor(0, 95, 106), BLACK,
MAGENTA,
GREEN,
BLUE,
];
use plotters::prelude::*;
let date = CURRENT_DATE.clone();
let path = format!("output/{}/{}", date, self.name.replace('/', "-"));
fs::create_dir_all(&path).expect("Can't create folder");
let output_name = format!("{}/{}_{}.png", &path, self.name.replace('/', "-"), rep);
let root = BitMapBackend::new(&output_name, (1024, 768)).into_drawing_area();
root.fill(&WHITE).expect("Can't fill the canvas");
let mut scatter_ctx = ChartBuilder::on(&root)
.caption(self.name.clone(), ("sans-serif", 30))
.margin(5)
.x_label_area_size(60)
.y_label_area_size(60)
.build_cartesian_2d(self.min_x..self.max_x, self.min_y..self.max_y)
.expect("Error Creating Chart");
scatter_ctx
.configure_mesh()
.disable_x_mesh()
.disable_y_mesh()
.y_desc(self.ylabel.clone())
.x_desc(self.xlabel.clone())
.draw()
.expect("Can't draw mesh");
let mut marker_id = 0;
let mut color_id = 0;
for (series_name, series) in &self.series {
match marker_id {
0 => scatter_ctx
.draw_series(
series
.iter()
.map(|(x, y)| Circle::new((*x, *y), 2.0, colors[color_id].filled())),
)
.expect("Can't draw series")
.label(series_name)
.legend(move |(x, y)| Circle::new((x, y), 3.0, colors[color_id].filled())),
1 => scatter_ctx
.draw_series(
series
.iter()
.map(|(x, y)| Cross::new((*x, *y), 3.0, colors[color_id].filled())),
)
.expect("Can't draw series")
.label(series_name)
.legend(move |(x, y)| Cross::new((x, y), 3.0, colors[color_id].filled())),
2 => scatter_ctx
.draw_series(series.iter().map(|(x, y)| {
TriangleMarker::new((*x, *y), 3.0, colors[color_id].filled())
}))
.expect("Can't draw series")
.label(series_name)
.legend(move |(x, y)| {
TriangleMarker::new((x, y), 3.0, colors[color_id].filled())
}),
_ => scatter_ctx
.draw_series(
series
.iter()
.map(|(x, y)| Circle::new((*x, *y), 2.0, colors[color_id].filled())),
)
.expect("Can't draw series")
.label(series_name)
.legend(move |(x, y)| Circle::new((x, y), 3.0, colors[color_id].filled())),
};
scatter_ctx
.draw_series(LineSeries::new(
series.iter().map(|(x, y)| (*x, *y)),
colors[color_id],
))
.expect("Can't draw series curve");
marker_id = (marker_id + 1) % n_markers;
color_id = (color_id + 1) % colors.len();
}
scatter_ctx
.configure_series_labels()
.position(SeriesLabelPosition::UpperRight)
.background_style(WHITE.mix(0.8))
.border_style(BLACK)
.draw()
.expect("Can't draw series labels");
root.present()
.unwrap_or_else(|_| panic!("Unable to write result to file: {}", output_name))
}
}
pub enum LogType {
Info,
Warning,
Error,
Critical,
}
impl fmt::Display for LogType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
LogType::Info => write!(f, "Info: "),
LogType::Warning => write!(f, "Warning: "),
LogType::Error => write!(f, "Error: "),
LogType::Critical => write!(f, "Critical: "),
}
}
}
#[doc(hidden)]
pub struct Log {
pub ltype: LogType,
pub body: String,
pub to_be_stored: bool,
}
impl fmt::Display for Log {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} {}", self.ltype, self.body)
}
}
use std::sync::mpsc::Sender;
lazy_static! {
#[doc(hidden)]
pub static ref DATA: Mutex<HashMap<String, PlotData>> = Mutex::new(HashMap::new());
#[doc(hidden)]
pub static ref CSV_SENDER: Mutex<Option<Sender<MessageType>>> = Mutex::new(None);
#[doc(hidden)]
pub static ref PLOT_NAMES: Mutex<std::collections::HashSet<(String, String, String)>> = Mutex::new(std::collections::HashSet::new());
#[doc(hidden)]
pub static ref LOGS: Mutex<Vec<Vec<Log>>> = Mutex::new(Vec::new());
#[doc(hidden)]
pub static ref DESCR: Mutex<String> = Mutex::new(String::new());
#[doc(hidden)]
pub static ref CURRENT_DATE: String = chrono::Local::now().format("%Y-%m-%d %H-%M-%S").to_string();
}
#[doc(hidden)]
pub struct Monitoring {
pub mem_used: Vec<f64>,
pub cpu_used: Vec<f64>,
}
#[doc(hidden)]
#[derive(Clone)]
pub enum MessageType {
Init,
AfterRep(u64, u64),
AfterStep(u64, f64, Duration),
Clear,
Consumed,
EndOfSimulation,
Quit,
Step,
Plot(String, String, f64, f64),
}
#[doc(hidden)]
impl Monitoring {
pub fn new() -> Self {
Monitoring {
mem_used: Vec::new(),
cpu_used: Vec::new(),
}
}
}
impl Default for Monitoring {
fn default() -> Self {
Self::new()
}
}
lazy_static! {
#[doc(hidden)]
pub static ref MONITOR: Arc<Mutex<Monitoring>> = Arc::new(Mutex::new(Monitoring::new()));
}
#[doc(hidden)]
pub use std::sync::mpsc::{self, RecvError, TryRecvError};
#[macro_export]
macro_rules! simulate {
($s:expr, $step:expr, $reps:expr $(, $flag:expr)?) => {{
let mut flag = true;
$(
flag = $flag;
)?
use std::time::Duration;
use $crate::*;
use $crate::engine::{schedule::*, state::*};
if flag {
let mut monitor = Arc::clone(&MONITOR);
let (sender_monitoring, recv_monitoring) = mpsc::channel();
let (sender_ui, recv_ui) = mpsc::channel();
let pid_main = match get_current_pid() {
Ok(pid) => pid,
Err(_) => panic!("Unable to get current pid"),
};
thread::spawn(move ||
loop {
let mut sys = System::new_all();
sys.refresh_all();
match sys.process(pid_main) {
Some(process) => {
let mem_used: f64 = ( sys.used_memory() as f64 / sys.total_memory() as f64) * 100.0;
log!(LogType::Info, format!("Memory used: {}%", mem_used));
for cpu in sys.cpus() {
log!(LogType::Info, format!("CPU {}: {}%", cpu.name(), cpu.cpu_usage()));
}
let cpu_used: f64 = process.cpu_usage() as f64;
{
let mut monitor = monitor.lock().unwrap();
if monitor.mem_used.len()>100 {
monitor.mem_used.remove(0);
monitor.cpu_used.remove(0);
}
monitor.mem_used.push(mem_used);
monitor.cpu_used.push(cpu_used);
}
},
None => {
log!(LogType::Critical, format!("Error on finding main pid"))
}
};
match recv_monitoring.try_recv() {
Ok(_) | Err(TryRecvError::Disconnected) => {
break;
}
Err(TryRecvError::Empty) => {}
}
});
let mut tui_operation: Arc<Mutex<MessageType>> = Arc::new(Mutex::new(MessageType::Consumed));
let mut tui_reps: Arc<Mutex<MessageType>> = Arc::new(Mutex::new(MessageType::Consumed));
let c_tui_operation = Arc::clone(&tui_operation);
let c_tui_reps = Arc::clone(&tui_reps);
let terminal_thread = thread::spawn(move || {
let tick_rate = Duration::from_millis(250);
let _ = enable_raw_mode();
let mut stdout = io::stdout();
let _ = execute!(stdout, EnterAlternateScreen, EnableMouseCapture).expect("Unable to enter alternate screen");
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend).unwrap();
let mut last_tick = Instant::now();
let mut ui = UI::new($step, $reps);
loop {
terminal.draw(|f| ui.draw(f)).expect("Error on drawing UI");
let timeout = tick_rate
.checked_sub(last_tick.elapsed())
.unwrap_or_else(|| Duration::from_secs(0));
if crossterm::event::poll(timeout).unwrap() {
if let Event::Key(key) = event::read().unwrap(){
match key.code {
KeyCode::Char(c) => ui.on_key(c),
KeyCode::Left => ui.on_left(),
KeyCode::Up => ui.on_up(),
KeyCode::Right => ui.on_right(),
KeyCode::Down => ui.on_down(),
_ => {
log!(LogType::Critical, format!("Invalid key pressed!"));
}
}
}
}
if ui.should_quit {
disable_raw_mode().expect("Error on disabling raw mode");
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
).expect("Error on leaving alternate screen");
terminal.show_cursor().expect("Error on enabling cursor");
break;
}
match recv_ui.try_recv() {
Ok(_) | Err(TryRecvError::Disconnected) => {
let op;
let rep;
{
op = c_tui_operation.lock().unwrap().clone();
rep = c_tui_reps.lock().unwrap().clone();
}
match op {
MessageType::AfterStep(step, progress, elapsed) => {
ui.on_tick(step, progress, elapsed);
{
*c_tui_operation.lock().unwrap() = MessageType::Consumed;
}
},
MessageType::Clear => {
terminal.clear().expect("Error on clearing terminal");
},
MessageType::Quit => {
terminal.clear().expect("Error on clearing terminal");
disable_raw_mode().expect("Error on disabling raw mode");
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
).expect("Error on leaving alternate screen");
terminal.show_cursor().expect("Error on enabling cursor");
break;
},
_ => {},
};
match rep {
MessageType::AfterRep(r, time) => {
ui.on_rep(
r,
time,
);
{
*c_tui_reps.lock().unwrap() = MessageType::Consumed;
}
},
_ => {},
}
},
Err(TryRecvError::Empty) => {}
}
};
});
let csv_recv: krabmaga::mpsc::Receiver<MessageType>;
let (s, r) = mpsc::channel();
{
let mut csv_send = CSV_SENDER.lock().expect("Error on lock");
*csv_send = Some(s.clone());
csv_recv = r;
}
let csv_thread = thread::spawn(move || {
let open_files = |rep_counter: &u32| {
let mut csv_writers: Vec<(String, Writer<File>)> = PLOT_NAMES.lock().unwrap().iter().map(|(name, x, y)| {
let date = CURRENT_DATE.clone();
let path = format!("output/{}/{}", date, name.replace("/", "-"));
fs::create_dir_all(&path).expect("Can't create folder");
let csv_name = format!("{}/{}_{}.csv", path, name.replace("/", "-"), rep_counter);
let mut writer = Writer::from_path(csv_name).expect("error on open the file path");
writer.write_record(&["series", &x, &y]).unwrap();
(name.replace("/", "-"), writer)
}).collect();
csv_writers
};
let mut rep_counter = 0;
let mut csv_writers = match csv_recv.recv().expect("Error receiving init csv message") {
MessageType::Quit => {
return;
},
_ => open_files(&0)
};
loop {
match csv_recv.recv(){
Ok(message) => {
match message {
MessageType::Init => {
csv_writers = open_files(&rep_counter);
},
MessageType::Plot(name, series, x, y) => {
for (n, writer) in &mut csv_writers {
if name.replace("/", "-") == *n {
writer.write_record(&[&series, &x.to_string(), &y.to_string()]).unwrap();
writer.flush().unwrap();
}
}
},
MessageType::EndOfSimulation => {
rep_counter += 1;
},
_ => break,
}
},
Err(_) => {
}
};
};
});
let sim_thread = thread::spawn(move || {
let mut s = $s;
let mut state = s.as_state_mut();
let n_step: u64 = $step;
for r in 0..$reps {
{
let mut logs = LOGS.lock().unwrap();
logs.insert(0, Vec::new());
}
{ DATA.lock().unwrap().clear(); }
{
let mut tui_operation = tui_operation.lock().unwrap();
*tui_operation = MessageType::Clear;
}
match sender_ui.send(()){
Ok(_) => {},
Err(_) => {
log!(LogType::Critical, format!("Simulation interrupted by user. Quitting..."), true);
break;
}
};
let start = std::time::Instant::now();
let mut schedule: Schedule = Schedule::new();
state.init(&mut schedule);
{
CSV_SENDER.lock().unwrap().as_ref().unwrap().send(MessageType::Init).expect("Error on communication with csv thread");
}
log!(LogType::Info, format!("#{} Simulation started", r), true);
let mut start = std::time::Instant::now();
for i in 0..n_step {
schedule.step(state);
{
let mut tui_operation = tui_operation.lock().unwrap();
*tui_operation = MessageType::AfterStep(
i,
(i + 1) as f64 / n_step as f64,
start.elapsed()
);
}
match sender_ui.send(()){
Ok(_) => {},
Err(_) => {
log!(LogType::Critical, format!("Simulation interrupted by user. Quitting..."), true);
break;
}
};
if state.end_condition(&mut schedule) {
{
let mut tui_operation = tui_operation.lock().unwrap();
*tui_operation = MessageType::Quit;
}
match sender_ui.send(()){
Ok(_) => {},
Err(_) => {
log!(LogType::Critical, format!("Simulation interrupted by user. Quitting..."), true);
break;
}
};
break;
}
}
let duration = start.elapsed();
log!(LogType::Info, format!("#{} Simulation ended in {}s", r, duration.as_secs_f64()), true);
{
CSV_SENDER.lock().unwrap().as_ref().unwrap().send(MessageType::EndOfSimulation).expect("Error on communication with csv thread");
}
{
let data = DATA.lock().unwrap();
for (key, plot) in data.iter() {
if plot.to_be_stored {
plot.store_plot(r)
}
}
}
let run_duration = start.elapsed();
{
let mut tui_reps = tui_reps.lock().unwrap();
*tui_reps = MessageType::AfterRep(
r,
((schedule.step as f32 / (run_duration.as_nanos() as f32 * 1e-9)) as u64),
);
}
match sender_ui.send(()){
Ok(_) => {},
Err(_) => {
log!(LogType::Critical, format!("Simulation interrupted by user. Quitting..."), true);
break;
}
};
}
{
CSV_SENDER.lock().unwrap().as_ref().unwrap().send(MessageType::Quit).expect("Error on communication with csv thread");
}
});
sim_thread.join().expect("Simulation thread panicked");
csv_thread.join().expect("CSV thread panicked");
let _ = sender_monitoring.send(()).expect("Monitoring thread panicked");
{
let mut logs = LOGS.lock().unwrap();
let date = CURRENT_DATE.clone();
fs::create_dir_all("output").expect("Can't create folder");
let log_path = format!("output/{}.log", date);
let mut f = File::create(log_path).expect("Can't create log file");
for log in logs.iter().flatten() {
if log.to_be_stored {
write!(f, "{}\n", log).expect("Can't write to log file");
}
}
}
terminal_thread.join().expect("Terminal thread panicked");
} else {
let mut s = $s;
let mut state = s.as_state_mut();
let n_step: u64 = $step;
for r in 0..$reps {
let mut schedule: Schedule = Schedule::new();
state.init(&mut schedule);
for i in 0..n_step {
schedule.step(state);
if state.end_condition(&mut schedule) {
break;
}
} } println!("Simulation finished!");
}
}}; }
#[macro_export]
macro_rules! description {
($description:expr) => {{
*DESCR.lock().unwrap() = $description.clone();
}};
}
#[macro_export]
macro_rules! plot {
($name:expr, $serie:expr, $x:expr, $y:expr $(, csv: $save_csv:expr)?) => {{
let mut data = DATA.lock().unwrap();
if data.contains_key(&$name) {
let mut pdata = data.get_mut(&$name).unwrap();
if !pdata.series.contains_key(&$serie) {
pdata.series.insert($serie.clone(), Vec::new());
}
let serie = pdata.series.get_mut(&$serie).unwrap();
serie.push(($x, $y));
if $x < pdata.min_x {
pdata.min_x = $x
};
if $x > pdata.max_x {
pdata.max_x = $x
};
if $y < pdata.min_y {
pdata.min_y = $y
};
if $y > pdata.max_y {
pdata.max_y = $y
};
$(
if $save_csv {
let send = CSV_SENDER
.lock()
.unwrap()
.as_ref()
.unwrap()
.send(MessageType::Plot($name.clone(), $serie.clone(), $x, $y))
.expect("Can't send to csv channel");
}
)?
}
}};
}
#[macro_export]
macro_rules! addplot {
($name:expr, $xlabel:expr, $ylabel:expr, plot: $save_plot:expr, csv: $save_csv:expr ) => {{
let mut data = DATA.lock().unwrap();
if !data.contains_key(&$name) {
data.insert($name, PlotData::new($name, $xlabel, $ylabel, $save_plot));
if $save_csv {
let mut names = PLOT_NAMES.lock().unwrap();
names.insert(($name, $xlabel, $ylabel));
}
}
}};
($name:expr, $xlabel:expr, $ylabel:expr, csv: $save_csv:expr ) => {{
addplot!($name, $xlabel, $ylabel, plot: false, csv: $save_csv);
}};
($name:expr, $xlabel:expr, $ylabel:expr, plot: $save_plot:expr) => {{
addplot!($name, $xlabel, $ylabel, plot: $save_plot, csv: false);
}};
($name:expr, $xlabel:expr, $ylabel:expr $(, $to_be_stored: expr)? ) => {{
let mut to_be_stored = false;
$(
to_be_stored = $to_be_stored;
)?
let mut data = DATA.lock().unwrap();
if !data.contains_key(&$name) {
data.insert($name, PlotData::new($name, $xlabel, $ylabel, to_be_stored));
}
}};
}
#[macro_export]
macro_rules! log {
($ltype:expr, $message:expr $(, $to_be_stored: expr)? ) => {{
use $crate::*;
let to_be_stored = false;
$(
let to_be_stored = $to_be_stored;
)?
{
let mut logs = LOGS.lock().unwrap();
if logs.is_empty() { logs.push(Vec::new()) }
logs[0].insert(
0,
Log {
ltype: $ltype,
body: $message,
to_be_stored,
},
);
}
}};
}
#[macro_export]
macro_rules! simulate_old {
($s:expr, $step:expr, $reps:expr $(, $info:expr)?) => {{
let mut s = $s;
let mut state = s.as_state_mut();
let n_step: u64 = $step;
let mut results: Vec<(Duration, f32)> = Vec::new();
let mut option = Info::Normal;
$(
option = $info;
)?
match option {
Info::Verbose => {
println!("\u{1F980} krABMaga\n");
println!(
"{0: >10}|{1: >9}| {2: >11}|{3: >10}|",
"#Rep", "Steps", "Steps/Seconds", "Time"
);
println!("--------------------------------------------------");
}
Info::Normal => {
println!("{esc}c", esc = 27 as char);
println!("\u{1F980} krABMaga\n");
println!(
"{0: >10}|{1: >9}| {2: >11}|{3: >10}|",
"#Rep", "Steps", "Avg. Steps/Seconds", "Avg. Time"
);
println!("----------------------------------------------------------------");
}
}
match option {
Info::Verbose => {}
Info::Normal => {
println!("{esc}c", esc = 27 as char);
}
}
for r in 0..$reps {
let mut schedule: Schedule = Schedule::new();
state.init(&mut schedule);
let start = std::time::Instant::now();
for i in 0..n_step {
schedule.step(state);
if state.end_condition(&mut schedule) {
break;
}
}
let run_duration = start.elapsed();
match option {
Info::Verbose => {}
Info::Normal => {
println!("{esc}c", esc = 27 as char);
println!("\u{1F980} krABMaga\n");
println!(
"{0: >10}|{1: >9}| {2: >11}|{3: >10}|",
"#Rep", "Steps", "Avg. Steps/Seconds", "Avg. Time"
);
println!("----------------------------------------------------------------");
}
}
let step_seconds =
format!("{:.0}", schedule.step as f32 / (run_duration.as_secs_f32()));
let time = format!("{:.4}", run_duration.as_secs_f32());
print!("{:width$}|", (r + 1), width = 14 - $reps.to_string().len());
print!(
"{:width$}|",
schedule.step,
width = 15 - n_step.to_string().len() - $reps.to_string().len()
);
print!("{:width$}", "", width = 13 - step_seconds.len());
results.push((
run_duration,
schedule.step as f32 / (run_duration.as_nanos() as f32 * 1e-9),
));
match option {
Info::Verbose => {
print!("{}|", step_seconds);
print!("{:width$}", "", width = 9 - time.len());
println!("{}s|", time);
}
Info::Normal => {
let mut avg_time = 0.0;
let mut avg_step_seconds = 0.0;
for (time, step_seconds) in &results {
avg_time += time.as_secs_f32();
avg_step_seconds += step_seconds;
}
avg_time /= results.len() as f32;
avg_step_seconds /= results.len() as f32;
let avg_step_seconds = format!("{:.2}", avg_step_seconds);
let avg_time = format!("{:.4}", avg_time);
print!("{}|", avg_step_seconds);
print!("{:width$}", "", width = 9 - avg_time.len());
println!("{}s|", avg_time);
}
}
}
results
}};
}
#[cfg(feature = "distributed_mpi")]
#[macro_export]
macro_rules! simulate_mpi {
($s:expr, $step:expr, $reps:expr $(, $info:expr)?) => {{
let world = UNIVERSE.world();
let mut s = $s;
let mut state = s.as_state_mut();
let n_step: u64 = $step;
let mut results: Vec<(Duration, f32)> = Vec::new();
let mut option = Info::Normal;
if world.rank() == 0{
$(
option = $info;
)?
}
for r in 0..$reps {
let mut schedule: Schedule = Schedule::new();
state.init(&mut schedule);
let start = std::time::Instant::now();
for i in 0..n_step {
schedule.step(state);
if state.end_condition(&mut schedule) {
break;
}
world.barrier();
}
let run_duration = start.elapsed();
if world.rank() == 0{
match option {
Info::Verbose => {}
Info::Normal => {
println!("{esc}c", esc = 27 as char);
println!("\u{1F980} krABMaga\n");
println!(
"{0: >10}|{1: >9}| {2: >11}|{3: >10}|",
"#Rep", "Steps", "Avg. Steps/Seconds", "Avg. Time"
);
println!("----------------------------------------------------------------");
}
}
let step_seconds =
format!("{:.0}", schedule.step as f32 / (run_duration.as_secs_f32()));
let time = format!("{:.4}", run_duration.as_secs_f32());
print!("{:width$}|", (r + 1), width = 14 - $reps.to_string().len());
print!(
"{:width$}|",
schedule.step,
width = 15 - n_step.to_string().len() - $reps.to_string().len()
);
print!("{:width$}", "", width = 13 - step_seconds.len());
results.push((
run_duration,
schedule.step as f32 / (run_duration.as_nanos() as f32 * 1e-9),
));
match option {
Info::Verbose => {
print!("{}|", step_seconds);
print!("{:width$}", "", width = 9 - time.len());
println!("{}s|", time);
}
Info::Normal => {
let mut avg_time = 0.0;
let mut avg_step_seconds = 0.0;
for (time, step_seconds) in &results {
avg_time += time.as_secs_f32();
avg_step_seconds += step_seconds;
}
avg_time /= results.len() as f32;
avg_step_seconds /= results.len() as f32;
let avg_step_seconds = format!("{:.2}", avg_step_seconds);
let avg_time = format!("{:.4}", avg_time);
print!("{}|", avg_step_seconds);
print!("{:width$}", "", width = 9 - avg_time.len());
println!("{}s|", avg_time);
}
}
}
unsafe {
mpi::ffi::MPI_Finalize();
}
}
results
}};
}
#[macro_use]
mod no_exported {
#[doc(hidden)]
#[macro_export]
macro_rules! replace_expr {
($_t:tt $sub:expr) => {
$sub
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! count_tts {
($($tts:tt)*) => {<[()]>::len(&[$(replace_expr!($tts ())),*])};
}
#[doc(hidden)]
#[macro_export]
macro_rules! build_configurations{
($n_conf: expr, $( $input:ident )*) =>{{
let mut config_table_index:Vec<Vec<usize>> = Vec::new();
let mut input_size:usize = 0;
let mut rep = $n_conf;
{
$(
let mut row:Vec<usize> = Vec::with_capacity($n_conf);
input_size = $input.len();
rep /= input_size;
let mut i = 0;
for _ in 0..$n_conf{
for _ in 0..rep{
row.push(i);
}
i = (i + 1) % input_size;
}
config_table_index.push(row);
)*
}
config_table_index
}};
}
}
pub fn write_csv<A: DataFrame>(name: &str, dataframe: &[A]) -> Result<(), Box<dyn Error>> {
let csv_name = format!("{}.csv", name);
let mut wtr = Writer::from_path(csv_name).expect("error on open the file path");
wtr.write_record(A::field_names())?;
for row in dataframe {
wtr.serialize(row.to_string())?;
}
Ok(())
}
#[doc(hidden)]
pub trait DataFrame {
fn field_names() -> &'static [&'static str];
fn to_string(&self) -> Vec<String>;
}
#[macro_export]
macro_rules! gen_param {
( $type:ty, $min:expr, $max:expr, $n:expr) => {{
let minimum: $type;
let maximum: $type;
minimum = $min;
maximum = $max;
let mut n = $n as usize;
let (minimum, maximum) = if minimum > maximum {
(maximum, minimum)
} else if minimum == maximum {
(minimum, maximum + 1 as $type)
} else {
(minimum, maximum)
};
if n == 0 {
n = 1;
}
let between = StandardUniform::from(minimum..maximum);
let mut rng = rand::rng();
let dist: Vec<$type> = between.sample_iter(&mut rng).take($n).collect();
dist
}};
( $type:ty, $min:expr, $max:expr) => {{
gen_param!($type, $min, $max, 1)
}};
}
#[macro_export]
macro_rules! load_csv {
($input_file: expr, $( $x:ident: $x_ty: ty ),*) =>{{
let mut rdr = Reader::from_path($input_file).expect("error on read a file from path");
$(
let mut $x: Vec<$x_ty> = Vec::new();
)*
for result in rdr.records() {
let record = result.expect("error on unwrap the record in csv file");
let mut i = 0;
$(
let x : $x_ty = record[i].parse().expect("error on parsing the record");
$x.push(x);
i += 1;
)*
}
let v = ($( $x, )*);
v
}};
}
#[macro_export]
macro_rules! check_reproducibility {
(
$state: expr,
$n_step: expr,
agents: { $( $agent:ident )* }
) => {
use $crate::engine::agent::Agent;
let mut schedule = Schedule::new();
let mut execution1: Vec<Vec<Box<dyn Agent>>> = Vec::new();
let mut state = $state.as_state_mut();
let n_step = $n_step as usize;
state.init(&mut schedule);
execution1.push(Vec::new());
execution1[0] = schedule.get_all_events();
for i in 0..n_step {
schedule.step(state);
execution1.push(Vec::new());
execution1[i+1] = schedule.get_all_events();
if state.end_condition(&mut schedule) {
break;
}
}
let mut schedule = Schedule::new();
let mut execution2: Vec<Vec<Box<dyn Agent>>> = Vec::new();
state.reset();
state.init(&mut schedule);
execution2.push(Vec::new());
execution2[0] = schedule.get_all_events();
for i in 0..n_step {
schedule.step(state);
execution2.push(Vec::new());
execution2[i+1] = schedule.get_all_events();
if state.end_condition(&mut schedule) {
break;
}
}
let mut equal = true;
for i in 0..execution1.len() {
if execution1[i].len() != execution2[i].len() {
if i == 0 {
println!("The first execution has {} agents, the second has {} agents", execution1[i].len(), execution2[i].len());
} else {
println!("The first execution has {} agents at step {}, the second has {} agents", execution1[i].len(), i+1, execution2[i].len());
}
equal = false;
break;
}
for j in 0..execution1[i].len() {
$(
if let Some(a1) = execution1[i][j].downcast_ref::<$agent>() {
if let Some(a2) = execution2[i][j].downcast_ref::<$agent>() {
if !a1.equals(a2) {
equal = false;
if i == 0 {
println!("Agents are not equal after the init function");
}
else {
println!("Agents are not equal in the step {}", i+1);
}
println!("Agent 1: {}", a1);
println!("Agent 2: {}", a2);
break;
}
}
}
)*
}
if !equal {
break;
}
}
if equal {
println!("The executions are equal");
}
};
}