use crate::utils::{iterate, resolve_domain, Sequence};
use polars::prelude::*;
use rayon::prelude::*;
use rayon::ThreadPoolBuilder;
pub fn find_equilibria(
f: &(impl Fn(f64) -> f64 + Sync),
domain: (f64, f64),
n_seeds: u32,
eq_tol: f64,
co_tol: f64,
n_ref: u32,
n_threads: usize,
) -> Vec<f64> {
let (lb, ub): (f64, f64) = domain;
let chunk_stepsize: f64 = (ub - lb) / n_threads as f64;
let chunks: Vec<Sequence> = (0..n_threads)
.map(|i: usize| {
Sequence::new(
lb + i as f64 * chunk_stepsize,
lb + (i + 1) as f64 * chunk_stepsize,
n_seeds / n_threads as u32,
)
})
.collect();
let pool = ThreadPoolBuilder::new()
.num_threads(n_threads)
.build()
.unwrap();
let approx_equilibria: Vec<Vec<f64>> = pool.install(|| {
chunks
.par_iter()
.map(|seq: &Sequence| {
let mut approx_equilibria: Vec<f64> = Vec::new();
for seed in *seq {
let seed_val: f64 = f(seed);
if (seed_val - seed).abs() < eq_tol {
approx_equilibria.push(seed);
}
}
approx_equilibria
})
.collect()
});
let approx_equilibria: Vec<f64> = approx_equilibria.into_iter().flatten().collect();
if approx_equilibria.is_empty() {
return approx_equilibria;
}
let intervals: Vec<(f64, f64)> = resolve_domain(&approx_equilibria, co_tol);
let mut equilibria: Vec<f64> = Vec::new();
for (lb, ub) in intervals {
let mut best: f64 = lb;
let mut best_diff: f64 = (f(best) - best).abs();
let seq: Sequence = Sequence::new(lb, ub, n_ref);
let mut cur_diff: f64;
for cur_eq in seq {
cur_diff = (f(cur_eq) - cur_eq).abs();
if cur_diff < best_diff {
best = cur_eq;
best_diff = cur_diff;
}
}
equilibria.push(best);
}
equilibria
}
pub fn find_periodics(
f: &(impl Fn(f64) -> f64 + Sync),
n_iter: i32,
domain: (f64, f64),
n_seeds: u32,
eq_tol: f64,
co_tol: f64,
n_ref: u32,
n_threads: usize,
) -> Vec<f64> {
let f = iterate(f, n_iter);
find_equilibria(&f, domain, n_seeds, eq_tol, co_tol, n_ref, n_threads)
}
pub fn find_behaviour(
f: &impl Fn(f64) -> f64,
equilibria: &[f64],
n_steps: i32,
s_tol: f64,
a_scale: f64,
) -> (Vec<bool>, Vec<bool>) {
let mut stable: Vec<bool> = Vec::with_capacity(equilibria.len());
let mut attractive: Vec<bool> = Vec::with_capacity(equilibria.len());
for e in equilibria {
let (cur_s, cur_a) = behaviour(&f, *e, n_steps, s_tol, a_scale);
stable.push(cur_s);
attractive.push(cur_a);
}
(stable, attractive)
}
fn behaviour(
f: &impl Fn(f64) -> f64,
e: f64,
n_steps: i32,
s_tol: f64,
a_scale: f64,
) -> (bool, bool) {
let mut stable: bool = true;
let mut attractive: bool = true;
for x0 in vec![e - s_tol, e + s_tol] {
let mut val: f64 = x0;
for _ in 0..n_steps {
val = f(val);
if stable && (val - e).abs() > s_tol + 1e-15 {
stable &= false;
}
}
if (val - e).abs() > s_tol * a_scale + 1e-15 {
attractive = false;
}
}
(stable, attractive)
}
pub fn find_bifurcations(
par_f: &(impl Fn(f64, f64) -> f64 + Sync),
par_space: (f64, f64),
n_pars: u32,
domain: (f64, f64),
n_seeds: u32,
eq_tol: f64,
co_tol: f64,
n_ref: u32,
n_steps: i32,
s_tol: f64,
a_scale: f64,
max_period: i32,
n_threads: usize,
) -> DataFrame {
let mut par_info: Vec<f64> = Vec::new();
let mut state_info: Vec<f64> = Vec::new();
let mut per_info: Vec<i32> = Vec::new();
let mut stable_info: Vec<bool> = Vec::new();
let mut attr_info: Vec<bool> = Vec::new();
let (p_lb, p_ub) = par_space;
let pars: Sequence = Sequence::new(p_lb, p_ub, n_pars);
for p in pars {
let f = move |x: f64| par_f(x, p);
let mut fixed_invariants: Vec<f64> = Vec::new();
for period in 1..=max_period {
let fp = iterate(f, period);
let mut equilibria: Vec<f64> =
find_equilibria(&fp, domain, n_seeds, eq_tol, co_tol, n_ref, n_threads);
for &e in &fixed_invariants {
equilibria = equilibria
.iter()
.filter_map(|new_e| {
if (e - new_e).abs() > co_tol {
Some(*new_e)
} else {
None
}
})
.collect();
}
let (stable, attractive) = find_behaviour(&fp, &equilibria, n_steps, s_tol, a_scale);
fixed_invariants.extend(&equilibria);
par_info.extend(&vec![p; equilibria.len()]);
state_info.extend(&equilibria);
per_info.extend(&vec![period; equilibria.len()]);
stable_info.extend(&stable);
attr_info.extend(&attractive);
}
}
let data: DataFrame = df!(
"parameter" => par_info,
"state" => state_info,
"stable" => stable_info,
"attractive" => attr_info,
"period" => per_info
)
.unwrap();
data
}
pub fn find_attractors(
par_f: &(impl Fn(f64, f64) -> f64 + Sync),
par_space: (f64, f64),
n_pars: u32,
x0: f64,
n_burnin: u32,
n_hist: u32
) -> DataFrame {
let mut data: Vec<DataFrame> = Vec::new();
let (p_lb, p_ub) = par_space;
let pars: Sequence = Sequence::new(p_lb, p_ub, n_pars);
for p in pars {
let f = move |x: f64| par_f(x, p);
let mut x_cur: f64 = x0;
for _ in 0..n_burnin {
x_cur = f(x_cur);
}
let mut hist: Vec<f64> = Vec::<f64>::with_capacity(n_hist as usize);
let mut pars: Vec<f64> = Vec::<f64>::with_capacity(n_hist as usize);
for _ in 0..n_hist {
x_cur = f(x_cur);
hist.push(x_cur);
pars.push(p);
}
let cur_data: DataFrame = df!(
"orbit" => hist,
"parameter" => pars
).unwrap();
data.push(cur_data);
}
let mut final_data: DataFrame = data.pop().unwrap();
for _ in 0..data.len() {
final_data.vstack_mut(&data.pop().unwrap()).unwrap();
}
final_data
}