Skip to content

Commit

Permalink
Chaos Functionality module (without gui) (#2)
Browse files Browse the repository at this point in the history
* copy code from chaos creation

* derive serde for configs and fix empty into iter

* use upstream versions egui_plotter and perplex_num
  • Loading branch information
tomtuamnuq authored Mar 5, 2024
1 parent b043558 commit d8d014e
Show file tree
Hide file tree
Showing 32 changed files with 7,733 additions and 10 deletions.
12 changes: 6 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ rand = "0.8"
nalgebra = "0.32"
num-dual = "0.8"

# switch manually between local git and upstream
egui-plotter = { git = "https://github.com/Gip-Gip/egui-plotter.git", branch = "main" }
perplex_num = { git = "https://github.com/tomtuamnuq/perplex_num.git", branch = "main" }
#egui-plotter = { path = "../egui-plotter" }
#perplex_num = { path = "../perplex_num" }

# native:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
env_logger = "0.10"
Expand All @@ -41,12 +47,6 @@ num_cpus = "1.16"
wasm-bindgen-futures = "0.4"
getrandom = { version = "0.2", features = ["js"] }

# switch manually between local git and upstream
egui-plotter = { git = "https://github.com/Gip-Gip/egui-plotter.git", branch = "main" }
perplex_num = { git = "https://github.com/tomtuamnuq/perplex_num.git", branch = "main" }
#egui-plotter = { path = "../egui-plotter" }
#perplex_num = { path = "../perplex_num" }

[profile.release]
opt-level = 3 # all optimizations
codegen-units = 1
Expand Down
4 changes: 2 additions & 2 deletions src/app.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/// We derive Deserialize/Serialize so we can persist app state on shutdown.
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(default)] // if we add new fields, give them default values when deserializing old state
// #[serde(default)] // if we add new fields, give them default values when deserializing old state
pub struct TemplateApp {
// Example stuff:
label: String,

#[serde(skip)] // This how you opt-out of serialization of a field
// #[serde(skip)] // This how you opt-out of serialization of a field
value: f32,
}

Expand Down
11 changes: 11 additions & 0 deletions src/chaos.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
pub mod benchmark;
pub mod data;
mod execution;
pub mod fractal;
pub mod functions;
pub mod labels;
mod particle;
pub use self::execution::*;
pub use self::functions::{OdeSolver, SimpleDiscreteMap};
pub use self::labels::ChaosDescription;
pub use self::particle::*;
262 changes: 262 additions & 0 deletions src/chaos/benchmark.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
use std::{
sync::{Arc, Mutex},
thread,
};
// TODO use wasm threads
use super::{
data::{ChaosData, ChaosDataVec, InitialDistributionConfig, ValidStateCheck},
execution::*,
};
use anyhow::Error;
use web_time::{Duration, Instant};
pub struct ChaosInitSchema {
pub num_samples: usize,
pub num_executions: usize,
pub init_distr: InitialDistributionConfig,
pub discrete_map_vec: Option<DiscreteMapVec>,
pub diff_system_vec: Option<OdeSystemSolverVec>,
pub pars: (&'static str, Vec<f64>),
}

pub struct ChaosBenchmarkRun {
runtime: Duration,
num_valid_end_states: usize,
}

impl ChaosBenchmarkRun {
pub fn runtime_millis(&self) -> usize {
self.runtime.as_millis() as usize
}

pub fn runtime_nanos(&self) -> u128 {
self.runtime.as_nanos()
}

pub fn num_valid_end_states(&self) -> usize {
self.num_valid_end_states
}
}

pub struct ChaosBenchmarkResult {
num_warmups: usize,
runs: Vec<Result<ChaosBenchmarkRun, Error>>, // contains warm-up runs as well
}

impl ChaosBenchmarkResult {
pub fn runs(&self) -> &Vec<Result<ChaosBenchmarkRun, Error>> {
&self.runs
}
pub fn number_of_warmups(&self) -> usize {
self.num_warmups
}
}

impl Default for ChaosBenchmarkResult {
fn default() -> Self {
let error_run = Err(Error::msg("Default init without runs!"));
Self {
num_warmups: 0,
runs: vec![error_run],
}
}
}

impl Default for ChaosInitSchema {
fn default() -> Self {
Self {
num_samples: 0,
num_executions: 0,
init_distr: InitialDistributionConfig::States(Vec::new()),
discrete_map_vec: None,
diff_system_vec: None,
pars: Default::default(),
}
}
}

pub fn chaos_benchmark(
chaos_init: &ChaosInitSchema,
num_iterations: usize,
num_warmups: usize,
) -> ChaosBenchmarkResult {
#[cfg(not(target_arch = "wasm32"))]
let threaded = true;
#[cfg(target_arch = "wasm32")]
let threaded = false;
let benchmark_run_cb: fn(&ChaosInitSchema) -> Result<ChaosBenchmarkRun, Error> = if threaded {
chaos_benchmark_threaded
} else {
chaos_benchmark_single
};
let total_num_runs = num_warmups + num_iterations;
let runs = (0..total_num_runs)
.map(|_| benchmark_run_cb(chaos_init))
.collect();
ChaosBenchmarkResult { num_warmups, runs }
}

fn chaos_benchmark_single(chaos_init: &ChaosInitSchema) -> Result<ChaosBenchmarkRun, Error> {
let ChaosInitSchema {
num_samples,
num_executions,
init_distr,
discrete_map_vec,
diff_system_vec,
..
} = chaos_init;
chaos_benchmark_run(
*num_samples,
*num_executions,
init_distr.clone(),
discrete_map_vec.clone(),
diff_system_vec.clone(),
)
}

fn chaos_benchmark_run(
num_samples: usize,
num_executions: usize,
init_distr: InitialDistributionConfig,
discrete_map_vec: Option<DiscreteMapVec>,
diff_system_vec: Option<OdeSystemSolverVec>,
) -> Result<ChaosBenchmarkRun, Error> {
let begin = Instant::now();
let mut controller = ChaosExecutionController::default();
controller.generate_initial_chaos_data(num_samples, init_distr)?;
if let Some(discrete_map_vec) = discrete_map_vec {
controller.set_discrete_mappers(discrete_map_vec)?;
} else if let Some(diff_system_vec) = diff_system_vec {
controller.set_differential_solvers(diff_system_vec)?;
};
controller.execute(num_executions)?;
let elapsed = begin.elapsed();
let chaos_data_vec = controller.get_chaos_data()?;
let num_valid_states = match chaos_data_vec {
ChaosDataVec::State1(data) => evaluate_chaos_data(data),
ChaosDataVec::State2(data) => evaluate_chaos_data(data),
ChaosDataVec::State3(data) => evaluate_chaos_data(data),
ChaosDataVec::State4(data) => evaluate_chaos_data(data),
ChaosDataVec::ParticleXY(data) => evaluate_chaos_data(data),
ChaosDataVec::ParticleXYZ(data) => evaluate_chaos_data(data),
ChaosDataVec::FractalComplex(data) => evaluate_chaos_data(data), // TODO specific for fractal - num iterations
ChaosDataVec::FractalDual(data) => evaluate_chaos_data(data), // TODO specific for fractal - num iterations
ChaosDataVec::FractalPerplex(data) => evaluate_chaos_data(data), // TODO specific for fractal - num iterations
ChaosDataVec::FractalQuaternion(data) => evaluate_chaos_data(data), // TODO specific for fractal - num iterations
};
Ok(ChaosBenchmarkRun {
runtime: elapsed,
num_valid_end_states: num_valid_states,
})
}

fn evaluate_chaos_data<V: ValidStateCheck>(data: Vec<&ChaosData<V>>) -> usize {
data.iter()
.map(|chaos_data| chaos_data.data_filtered().len())
.sum()
}

fn chaos_benchmark_threaded(chaos_init: &ChaosInitSchema) -> Result<ChaosBenchmarkRun, Error> {
let ChaosInitSchema {
num_samples,
num_executions,
init_distr,
discrete_map_vec,
diff_system_vec,
..
} = chaos_init;
let begin = Instant::now();
#[cfg(not(target_arch = "wasm32"))]
let num_threads = num_cpus::get();
#[cfg(target_arch = "wasm32")]
let num_threads = 1; // TODO wasm threads
log::debug!("Starting benchmark with {:?} threads.", num_threads);
let (num_samples_per_thread, num_remaining_samples) = (
num_samples.div_euclid(num_threads),
num_samples.rem_euclid(num_threads),
);
log::debug!("Each thread creates {:?} samples.", num_samples_per_thread);
let num_valid_states = Arc::new(Mutex::new(Vec::with_capacity(num_threads)));
thread::scope(|s| {
for i in 0..num_threads {
let num_valid_states = Arc::clone(&num_valid_states);
let num_samples_this_thread = if i == 0 {
num_samples_per_thread + num_remaining_samples
} else {
num_samples_per_thread
};
s.spawn(move || {
log::debug!("Thread {:?} started.", i);
let begin_thread = Instant::now();
let result = chaos_benchmark_run(
num_samples_this_thread,
*num_executions,
init_distr.clone(),
discrete_map_vec.clone(),
diff_system_vec.clone(),
);
if let Ok(mut num_valid_states) = num_valid_states.lock() {
num_valid_states.push(result);
}
log::debug!(
"Thread {:?} finished after {:?} ms.",
i,
begin_thread.elapsed().as_millis()
);
});
}
});
let elapsed = begin.elapsed();
let mut total_num_valid_end_states = 0;
if let Ok(num_valid_states) = num_valid_states.lock() {
for res in num_valid_states.iter() {
match res {
Ok(res) => {
total_num_valid_end_states += res.num_valid_end_states();
}
Err(e) => {
return Err(Error::msg(format!("A thread caused an error: {e}")));
}
}
}
};
Ok(ChaosBenchmarkRun {
runtime: elapsed,
num_valid_end_states: total_num_valid_end_states,
})
}

#[cfg(test)]
mod tests {
use super::*;
use crate::chaos::{
data::InitialDistributionVariant,
functions::{Logistic, SimpleDiscreteMap},
};
#[test]
fn test_chaos_benchmark() {
let num_samples = 11;
let map = SimpleDiscreteMap::new(Logistic::default());
let chaos_init = ChaosInitSchema {
num_samples,
num_executions: 2,
init_distr: InitialDistributionConfig::States(vec![
InitialDistributionVariant::default(),
]),
discrete_map_vec: Some(DiscreteMapVec::Logistic(vec![map])),
diff_system_vec: None,
pars: Default::default(),
};
let benchmark_result = chaos_benchmark_single(&chaos_init);
assert!(benchmark_result.is_ok());
if let Ok(res) = benchmark_result {
assert_eq!(res.num_valid_end_states, num_samples);
assert!(res.runtime.as_nanos() > 0);
}
let benchmark_result = chaos_benchmark_threaded(&chaos_init);
assert!(benchmark_result.is_ok());
if let Ok(res) = benchmark_result {
assert_eq!(res.num_valid_end_states, num_samples);
assert!(res.runtime.as_nanos() > 0);
}
}
}
9 changes: 9 additions & 0 deletions src/chaos/data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
mod chaos_data;
mod chaos_states;
mod data_variants;
#[allow(clippy::derivable_impls)]
mod initial_distribution;
pub use self::chaos_data::*;
pub use self::chaos_states::*;
pub use self::data_variants::*;
pub use self::initial_distribution::*;
Loading

0 comments on commit d8d014e

Please sign in to comment.