#![allow(non_camel_case_types)]
use crate::global::THRESHOLD as T;
use crate::numerical::BVP_Damp::BVP_traits::{
Fun,
FunEnum,
Jac,
JacEnum,
MatrixType,
VectorType,
convert_to_fun, };
use crate::numerical::BVP_Damp::BVP_utils::elapsed_time;
use crate::symbolic::symbolic_engine::Expr;
use faer::col::Col;
use faer::col::ColRef;
use faer::sparse::{SparseColMat, Triplet};
use log::info;
use nalgebra::sparse::CsMatrix;
use nalgebra::{DMatrix, DVector};
use rayon::prelude::*;
use sprs::{CsMat, CsVec};
use std::collections::{HashMap, HashSet};
use std::sync::Mutex;
use std::time::Instant;
use tabled::{builder::Builder, settings::Style};
pub struct Jacobian {
pub vector_of_functions: Vec<Expr>,
pub lambdified_functions: Vec<Box<dyn Fn(Vec<f64>) -> f64>>,
pub method: String,
pub vector_of_variables: Vec<Expr>,
pub variable_string: Vec<String>,
pub symbolic_jacobian: Vec<Vec<Expr>>,
pub lambdified_jac_element: Option<Box<dyn Fn(f64, usize, usize) -> f64>>,
pub jac_function: Option<Box<dyn Jac>>,
pub residiual_function: Box<dyn Fun>,
pub bounds: Option<Vec<(f64, f64)>>,
pub rel_tolerance_vec: Option<Vec<f64>>,
pub bandwidth: Option<(usize, usize)>,
pub variables_for_all_disrete: Vec<Vec<String>>,
pub BC_pos_n_values: Vec<(usize, usize, f64)>,
}
impl Jacobian {
pub fn new() -> Self {
let fun0: Box<dyn Fn(f64, &DVector<f64>) -> DVector<f64>> =
Box::new(|_x, y: &DVector<f64>| y.clone());
let boxed_fun: Box<dyn Fun> = Box::new(FunEnum::Dense(fun0));
Self {
vector_of_functions: Vec::new(),
lambdified_functions: Vec::new(),
method: String::new(),
vector_of_variables: Vec::new(),
variable_string: Vec::new(),
symbolic_jacobian: Vec::new(),
lambdified_jac_element: None,
jac_function: None,
residiual_function: boxed_fun,
bounds: None,
rel_tolerance_vec: None,
bandwidth: None,
variables_for_all_disrete: Vec::new(),
BC_pos_n_values: Vec::new(),
}
}
pub fn from_vectors(&mut self, vector_of_functions: Vec<Expr>, variable_string: Vec<String>) {
self.vector_of_functions = vector_of_functions;
self.variable_string = variable_string.clone();
self.vector_of_variables =
Expr::parse_vector_expression(variable_string.iter().map(|s| s.as_str()).collect());
println!(" {:?}", self.vector_of_functions);
println!(" {:?}", self.vector_of_variables);
}
pub fn calc_jacobian_parallel_smart_optimized_with_given_bandwidth(&mut self) {
assert!(self.variables_for_all_disrete.len() > 0);
assert!(
!self.vector_of_functions.is_empty(),
"vector_of_functions is empty"
);
assert!(
!self.vector_of_variables.is_empty(),
"vector_of_variables is empty"
);
let variable_string_vec = &self.variable_string;
let bandwidth = self.bandwidth;
let vector_of_functions_len = self.vector_of_functions.len();
let vector_of_variables_len = self.vector_of_variables.len();
let variable_sets: Vec<HashSet<&String>> = self
.variables_for_all_disrete
.iter()
.map(|vars| vars.iter().collect())
.collect();
let new_jac: Vec<Vec<Expr>> = (0..vector_of_functions_len)
.into_par_iter()
.map(|i| {
let (right_border, left_border) = if let Some((kl, ku)) = bandwidth {
let right_border = std::cmp::min(i + ku + 1, vector_of_variables_len);
let left_border = if i as i32 - (kl as i32) - 1 < 0 {
0
} else {
i - kl - 1
};
(right_border, left_border)
} else {
(vector_of_variables_len, 0)
};
let mut row = vec![Expr::Const(0.0); vector_of_variables_len];
for j in left_border..right_border {
let variable = &variable_string_vec[j];
if variable_sets[i].contains(variable) {
let mut partial = Expr::diff(&self.vector_of_functions[i], variable);
partial = partial.simplify();
row[j] = partial;
}
}
row
})
.collect();
self.symbolic_jacobian = new_jac;
}
pub fn calc_jacobian_parallel_smart_optimized(&mut self) {
assert!(self.variables_for_all_disrete.len() > 0);
assert!(
!self.vector_of_functions.is_empty(),
"vector_of_functions is empty"
);
assert!(
!self.vector_of_variables.is_empty(),
"vector_of_variables is empty"
);
let variable_string_vec = &self.variable_string;
let variable_sets: Vec<HashSet<&String>> = self
.variables_for_all_disrete
.iter()
.map(|vars| vars.iter().collect())
.collect();
let new_jac: Vec<Vec<Expr>> = self
.vector_of_functions
.par_iter()
.enumerate()
.map(|(i, function)| {
let mut vector_of_partial_derivatives =
Vec::with_capacity(self.vector_of_variables.len());
for j in 0..self.vector_of_variables.len() {
let variable = &variable_string_vec[j];
if variable_sets[i].contains(variable) {
let mut partial = Expr::diff(function, variable);
partial = partial.simplify();
vector_of_partial_derivatives.push(partial);
} else {
vector_of_partial_derivatives.push(Expr::Const(0.0));
}
}
vector_of_partial_derivatives
})
.collect();
self.symbolic_jacobian = new_jac;
}
pub fn calc_jacobian_parallel_smart(&mut self) {
assert!(self.variables_for_all_disrete.len() > 0);
assert!(
!self.vector_of_functions.is_empty(),
"vector_of_functions is empty"
);
assert!(
!self.vector_of_variables.is_empty(),
"vector_of_variables is empty"
);
let variable_string_vec = self.variable_string.clone();
let new_jac: Vec<Vec<Expr>> = self
.vector_of_functions
.par_iter()
.enumerate()
.map(|(i, function)| {
let mut vector_of_partial_derivatives = Vec::new();
for j in 0..self.vector_of_variables.len() {
let variable = &variable_string_vec[j]; let list_of_vaiables_for_this_eq = &self.variables_for_all_disrete[i]; if list_of_vaiables_for_this_eq.contains(variable) {
let mut partial = Expr::diff(&function, variable);
partial = partial.simplify();
vector_of_partial_derivatives.push(partial);
} else {
vector_of_partial_derivatives.push(Expr::Const(0.0));
}
}
vector_of_partial_derivatives
})
.collect();
self.symbolic_jacobian = new_jac;
}
pub fn calc_jacobian_parallel(&mut self) {
assert!(
!self.vector_of_functions.is_empty(),
"vector_of_functions is empty"
);
assert!(
!self.vector_of_variables.is_empty(),
"vector_of_variables is empty"
);
let variable_string_vec = self.variable_string.clone();
let new_jac: Vec<Vec<Expr>> = self
.vector_of_functions
.par_iter()
.map(|function| {
let mut vector_of_partial_derivatives = Vec::new();
for j in 0..self.vector_of_variables.len() {
let mut partial = Expr::diff(&function, &variable_string_vec[j]);
partial = partial.simplify();
vector_of_partial_derivatives.push(partial);
}
vector_of_partial_derivatives
})
.collect();
self.symbolic_jacobian = new_jac;
}
pub fn lambdify_residual_VectorType(&mut self, arg: &str, variable_str: Vec<&str>) {
let vector_of_functions = &self.vector_of_functions;
fn f(
vector_of_functions: Vec<Expr>,
_arg: String,
variable_str: Vec<String>,
) -> Box<dyn Fn(f64, &dyn VectorType) -> Box<dyn VectorType>> {
Box::new(move |_x: f64, v: &dyn VectorType| -> Box<dyn VectorType> {
let mut result = v.zeros(vector_of_functions.len());
for (i, func) in vector_of_functions.iter().enumerate() {
let func = Expr::lambdify_borrowed_thread_safe(
&func,
variable_str
.iter()
.map(|s| s.as_str())
.collect::<Vec<_>>()
.as_slice(),
);
let v: Vec<f64> = v.iterate().collect();
let value = func(v.as_slice());
result = result.assign_value(i, value);
}
result
})
}
let fun: Box<dyn Fn(f64, &dyn VectorType) -> Box<dyn VectorType>> = f(
vector_of_functions.to_owned(),
arg.to_string(),
variable_str.iter().map(|s| s.to_string()).collect(),
);
let residual_function = convert_to_fun(fun);
self.residiual_function = residual_function;
}
pub fn jacobian_generate_generic_par(
jac: Vec<Vec<Expr>>,
vector_of_functions_len: usize,
vector_of_variables_len: usize,
variable_str: Vec<String>,
_arg: String,
bandwidth: Option<(usize, usize)>,
) -> Box<dyn Fn(f64, &(dyn VectorType + Send + Sync)) -> Box<dyn MatrixType>> {
Box::new(
move |_x: f64, v: &(dyn VectorType + Send + Sync)| -> Box<dyn MatrixType> {
let mut vector_of_derivatives =
vec![0.0; vector_of_functions_len * vector_of_variables_len];
let vector_of_derivatives_mutex = std::sync::Mutex::new(&mut vector_of_derivatives);
let mut vector_of_triplets = Vec::new();
let vector_of_triplets_mutex = std::sync::Mutex::new(&mut vector_of_triplets);
(0..vector_of_functions_len).into_par_iter().for_each(|i| {
let (right_border, left_border) = if let Some((kl, ku)) = bandwidth {
let right_border = std::cmp::min(i + ku + 1, vector_of_variables_len);
let left_border = if i as i32 - (kl as i32) - 1 < 0 {
0
} else {
i - kl - 1
};
(right_border, left_border)
} else {
let right_border = vector_of_variables_len;
let left_border = 0;
(right_border, left_border)
};
for j in left_border..right_border {
let partial_func = Expr::lambdify_borrowed_thread_safe(
&jac[i][j],
variable_str
.iter()
.map(|s| s.as_str())
.collect::<Vec<_>>()
.as_slice(),
);
let v_vec: Vec<f64> = v.iterate().collect();
let _vec_type = &v.vec_type();
let P = partial_func(v_vec.as_slice());
if P.abs() > T {
vector_of_derivatives_mutex.lock().unwrap()
[i * vector_of_functions_len + j] = P;
let triplet = (i, j, P);
vector_of_triplets_mutex.lock().unwrap().push(triplet);
}
}
});
let new_function_jacobian: Box<dyn MatrixType> = v.from_vector(
vector_of_functions_len,
vector_of_variables_len,
&vector_of_derivatives,
vector_of_triplets,
);
new_function_jacobian
},
) }
pub fn lambdify_jacobian_generic(&mut self, arg: &str, variable_str: Vec<&str>) {
let symbolic_jacobian = self.symbolic_jacobian.clone();
let symbolic_jacobian_rc = symbolic_jacobian.clone();
let vector_of_functions_len = self.vector_of_functions.len();
let vector_of_variables_len = self.vector_of_variables.len();
let bandwidth = self.bandwidth;
let _new_jac = Jacobian::jacobian_generate_generic_par(
symbolic_jacobian_rc,
vector_of_functions_len,
vector_of_variables_len,
variable_str.iter().map(|s| s.to_string()).collect(),
arg.to_string(),
bandwidth,
);
}
pub fn lambdify_jacobian_DMatrix_par(&mut self, arg: &str, variable_str: Vec<&str>) {
let jac = self.symbolic_jacobian.clone();
let vector_of_functions_len = self.vector_of_functions.len();
let vector_of_variables_len = self.vector_of_variables.len();
let bandwidth = self.bandwidth;
let jacobian_positions: Vec<(usize, usize, Box<dyn Fn(&[f64]) -> f64 + Send + Sync>)> = (0
..vector_of_functions_len)
.into_par_iter()
.flat_map(|i| {
let (right_border, left_border) = if let Some((kl, ku)) = bandwidth {
let right_border = std::cmp::min(i + ku + 1, vector_of_variables_len);
let left_border = if i as i32 - (kl as i32) - 1 < 0 {
0
} else {
i - kl - 1
};
(right_border, left_border)
} else {
(vector_of_variables_len, 0)
};
(left_border..right_border)
.filter_map(|j| {
let symbolic_partial_derivative = &jac[i][j];
if !symbolic_partial_derivative.is_zero() {
let compiled_func: Box<dyn Fn(&[f64]) -> f64 + Send + Sync> =
Expr::lambdify_borrowed_thread_safe(
&symbolic_partial_derivative,
variable_str.as_slice(),
);
Some((i, j, compiled_func))
} else {
None
}
})
.collect::<Vec<_>>()
})
.collect();
let new_jac = Box::new(move |_x: f64, v: &DVector<f64>| -> DMatrix<f64> {
let v_vec: Vec<f64> = v.iter().cloned().collect();
let mut matrix = DMatrix::zeros(vector_of_functions_len, vector_of_variables_len);
let matrix_mutex = Mutex::new(&mut matrix);
jacobian_positions
.par_iter()
.for_each(|(i, j, compiled_func)| {
let P = compiled_func(v_vec.as_slice());
if P.abs() > T {
let mut mat = matrix_mutex.lock().unwrap();
mat[(*i, *j)] = P;
}
});
matrix
});
let boxed_jac: Box<dyn Jac> = Box::new(JacEnum::Dense(new_jac));
self.jac_function = Some(boxed_jac);
}
pub fn lambdify_residual_DVector(&mut self, arg: &str, variable_str: Vec<&str>) {
let vector_of_functions = &self.vector_of_functions;
let variable_str_owned: Vec<String> = variable_str.iter().map(|s| s.to_string()).collect();
let compiled_functions: Vec<Box<dyn Fn(&[f64]) -> f64 + Send + Sync>> = vector_of_functions
.par_iter()
.map(|func| {
Expr::lambdify_borrowed_thread_safe(
func,
variable_str_owned
.iter()
.map(|s| s.as_str())
.collect::<Vec<_>>()
.as_slice(),
)
})
.collect();
let fun = Box::new(move |_x: f64, v: &DVector<f64>| -> DVector<f64> {
let v_vec: Vec<f64> = v.iter().cloned().collect();
let result: Vec<_> = compiled_functions
.par_iter()
.map(|func| func(v_vec.as_slice()))
.collect();
DVector::from_vec(result)
});
let boxed_fun: Box<dyn Fun> = Box::new(FunEnum::Dense(fun));
self.residiual_function = boxed_fun;
}
pub fn jacobian_generate_CsMat(
jac: Vec<Vec<Expr>>,
vector_of_functions_len: usize,
vector_of_variables_len: usize,
variable_str: Vec<String>,
_arg: String,
) -> Box<dyn FnMut(f64, &CsVec<f64>) -> CsMat<f64>> {
Box::new(move |_x: f64, v: &CsVec<f64>| -> CsMat<f64> {
let mut new_function_jacobian: CsMat<f64> =
CsMat::zero((vector_of_functions_len, vector_of_variables_len));
for i in 0..vector_of_functions_len {
for j in 0..vector_of_variables_len {
let partial_func = Expr::lambdify_borrowed_thread_safe(
&jac[i][j],
variable_str
.iter()
.map(|s| s.as_str())
.collect::<Vec<_>>()
.as_slice(),
);
let v_vec: Vec<f64> = v.to_dense().to_vec();
new_function_jacobian.insert(j, i, partial_func(v_vec.as_slice()));
}
}
new_function_jacobian
})
} pub fn lambdify_jacobian_CsMat(&mut self, arg: &str, variable_str: Vec<&str>) {
let symbolic_jacobian = self.symbolic_jacobian.clone();
let symbolic_jacobian_rc = symbolic_jacobian.clone();
let vector_of_functions_len = self.vector_of_functions.len();
let vector_of_variables_len = self.vector_of_variables.len();
let new_jac = Jacobian::jacobian_generate_CsMat(
symbolic_jacobian_rc,
vector_of_functions_len,
vector_of_variables_len,
variable_str.iter().map(|s| s.to_string()).collect(),
arg.to_string(),
);
let boxed_jac: Box<dyn Jac> = Box::new(JacEnum::Sparse_1(new_jac));
self.jac_function = Some(boxed_jac);
}
pub fn lambdify_residual_CsVec(&mut self, arg: &str, variable_str: Vec<&str>) {
let vector_of_functions = &self.vector_of_functions;
fn f(
vector_of_functions: Vec<Expr>,
arg: String,
variable_str: Vec<String>,
) -> Box<dyn Fn(f64, &CsVec<f64>) -> CsVec<f64> + 'static> {
Box::new(move |x: f64, v: &CsVec<f64>| -> CsVec<f64> {
let mut result: CsVec<f64> = CsVec::empty(vector_of_functions.len());
for (i, func) in vector_of_functions.iter().enumerate() {
let func = Expr::lambdify_IVP_owned(
func.to_owned(),
arg.as_str(),
variable_str
.iter()
.map(|s| s.as_str())
.collect::<Vec<_>>()
.clone(),
);
result.append(i, func(x, v.to_dense().to_vec()));
}
result
}) } let fun = f(
vector_of_functions.to_owned(),
arg.to_string(),
variable_str.clone().iter().map(|s| s.to_string()).collect(),
);
let boxed_fun: Box<dyn Fun> = Box::new(FunEnum::Sparse_1(fun));
self.residiual_function = boxed_fun;
}
pub fn jacobian_generate_CsMatrix(
jac: Vec<Vec<Expr>>,
vector_of_functions_len: usize,
vector_of_variables_len: usize,
variable_str: Vec<String>,
_arg: String,
) -> Box<dyn FnMut(f64, &DVector<f64>) -> CsMatrix<f64>> {
Box::new(move |_x: f64, v: &DVector<f64>| -> CsMatrix<f64> {
let _number_of_possible_non_zero: usize = jac.len();
let mut new_function_jacobian: DMatrix<f64> =
DMatrix::zeros(vector_of_functions_len, vector_of_variables_len);
for i in 0..vector_of_functions_len {
for j in 0..vector_of_variables_len {
let partial_func = Expr::lambdify_borrowed_thread_safe(
&jac[i][j],
variable_str
.iter()
.map(|s| s.as_str())
.collect::<Vec<_>>()
.as_slice(),
);
let v_vec: Vec<f64> = v.iter().cloned().collect();
new_function_jacobian[(i, j)] = partial_func(v_vec.as_slice());
}
}
new_function_jacobian.into()
})
} pub fn lambdify_jacobian_CsMatrix(&mut self, arg: &str, variable_str: Vec<&str>) {
let symbolic_jacobian = self.symbolic_jacobian.clone();
let symbolic_jacobian_rc = symbolic_jacobian.clone();
let vector_of_functions_len = self.vector_of_functions.len();
let vector_of_variables_len = self.vector_of_variables.len();
let new_jac = Jacobian::jacobian_generate_CsMatrix(
symbolic_jacobian_rc,
vector_of_functions_len,
vector_of_variables_len,
variable_str.iter().map(|s| s.to_string()).collect(),
arg.to_string(),
);
let boxed_jac: Box<dyn Jac> = Box::new(JacEnum::Sparse_2(new_jac));
self.jac_function = Some(boxed_jac);
}
pub fn lambdify_jacobian_SparseColMat_modified(&mut self, _arg: &str, variable_str: Vec<&str>) {
let symbolic_jacobian = self.symbolic_jacobian.clone();
let vector_of_functions_len = self.vector_of_functions.len();
let vector_of_variables_len = self.vector_of_variables.len();
let bandwidth = self.bandwidth;
let mut compiled_jacobian_elements = Vec::new();
for i in 0..vector_of_functions_len {
let (right_border, left_border) = if let Some((kl, ku)) = bandwidth {
let right_border = std::cmp::min(i + ku + 1, vector_of_variables_len);
let left_border = if i as i32 - (kl as i32) - 1 < 0 {
0
} else {
i - kl - 1
};
(right_border, left_border)
} else {
(vector_of_variables_len, 0)
};
for j in left_border..right_border {
let symbolic_partial_derivative = &symbolic_jacobian[i][j];
if !symbolic_partial_derivative.is_zero() {
let compiled_func = Expr::lambdify_borrowed_thread_safe(
&symbolic_partial_derivative,
variable_str.as_slice(),
);
compiled_jacobian_elements.push((i, j, compiled_func));
}
}
}
let new_jac = Box::new(move |_x: f64, v: &Col<f64>| -> SparseColMat<usize, f64> {
let v_vec: Vec<f64> = v.iter().cloned().collect();
let mut vector_of_triplets = Vec::new();
for (i, j, func) in &compiled_jacobian_elements {
let P = func(v_vec.as_slice());
if P.abs() > T {
vector_of_triplets.push(Triplet::new(*i, *j, P));
}
}
SparseColMat::try_new_from_triplets(
vector_of_functions_len,
vector_of_variables_len,
vector_of_triplets.as_slice(),
)
.unwrap()
});
let boxed_jac: Box<dyn Jac> = Box::new(JacEnum::Sparse_3(new_jac));
self.jac_function = Some(boxed_jac);
}
pub fn lambdify_jacobian_SparseColMat_parallel2(
&mut self,
_arg: &str,
variable_str: Vec<&str>,
) {
let jac = self.symbolic_jacobian.clone();
let vector_of_functions_len = self.vector_of_functions.len();
let vector_of_variables_len = self.vector_of_variables.len();
let bandwidth = self.bandwidth;
let jacobian_positions: Vec<(usize, usize, Box<dyn Fn(&[f64]) -> f64 + Send + Sync>)> = (0
..vector_of_functions_len)
.into_par_iter()
.flat_map(|i| {
let variable_str = variable_str.clone();
let (right_border, left_border) = if let Some((kl, ku)) = bandwidth {
let right_border = std::cmp::min(i + ku + 1, vector_of_variables_len);
let left_border = if i as i32 - (kl as i32) - 1 < 0 {
0
} else {
i - kl - 1
};
(right_border, left_border)
} else {
(vector_of_variables_len, 0)
};
(left_border..right_border)
.filter_map(|j| {
let symbolic_partial_derivative = &jac[i][j];
if !symbolic_partial_derivative.is_zero() {
let compiled_func: Box<dyn Fn(&[f64]) -> f64 + Send + Sync> =
Expr::lambdify_borrowed_thread_safe(
&symbolic_partial_derivative,
variable_str.as_slice(),
);
Some((i, j, compiled_func))
} else {
None
}
})
.collect::<Vec<_>>()
})
.collect();
let new_jac = Box::new(move |_x: f64, v: &Col<f64>| -> SparseColMat<usize, f64> {
let v_vec: Vec<f64> = v.iter().cloned().collect();
let triplets_mutex = std::sync::Mutex::new(Vec::new());
jacobian_positions
.par_iter()
.for_each(|(i, j, compiled_func)| {
let P = compiled_func(v_vec.as_slice());
if P.abs() > T {
let mut triplets = triplets_mutex.lock().unwrap();
triplets.push(Triplet::new(*i, *j, P));
}
});
let triplets = triplets_mutex.lock().unwrap();
SparseColMat::try_new_from_triplets(
vector_of_functions_len,
vector_of_variables_len,
triplets.as_slice(),
)
.unwrap()
});
let boxed_jac: Box<dyn Jac> = Box::new(JacEnum::Sparse_3(new_jac));
self.jac_function = Some(boxed_jac);
}
pub fn lambdify_residual_Col_modified(&mut self, _arg: &str, variable_str: Vec<&str>) {
let vector_of_functions = &self.vector_of_functions;
let compiled_functions: Vec<_> = vector_of_functions
.iter()
.map(|func| Expr::lambdify_borrowed_thread_safe(&func, variable_str.as_slice()))
.collect();
let fun = Box::new(move |_x: f64, v: &Col<f64>| -> Col<f64> {
let v_vec: Vec<f64> = v.iter().cloned().collect();
let result: Vec<_> = compiled_functions
.iter()
.map(|func| func(v_vec.as_slice()))
.collect();
ColRef::from_slice(result.as_slice()).to_owned()
});
let boxed_fun: Box<dyn Fun> = Box::new(FunEnum::Sparse_3(fun));
self.residiual_function = boxed_fun;
}
pub fn lambdify_residual_Col_parallel2(&mut self, _arg: &str, variable_str: Vec<&str>) {
let vector_of_functions = &self.vector_of_functions;
let variable_str_owned: Vec<String> = variable_str.iter().map(|s| s.to_string()).collect();
let compiled_functions: Vec<Box<dyn Fn(&[f64]) -> f64 + Send + Sync>> = vector_of_functions
.into_par_iter()
.map(|func| {
let compiled = Expr::lambdify_borrowed_thread_safe(
func,
variable_str_owned
.iter()
.map(|s| s.as_str())
.collect::<Vec<_>>()
.as_slice(),
);
compiled
})
.collect();
let fun = Box::new(move |_x: f64, v: &Col<f64>| -> Col<f64> {
let v_vec: Vec<f64> = v.iter().cloned().collect();
let result: Vec<_> = compiled_functions
.par_iter()
.map(|func| func(v_vec.as_slice()))
.collect();
ColRef::from_slice(result.as_slice()).to_owned()
});
let boxed_fun: Box<dyn Fun> = Box::new(FunEnum::Sparse_3(fun));
self.residiual_function = boxed_fun;
}
pub fn remove_numeric_suffix(input: &str) -> String {
if let Some(pos) = input.rfind('_') {
input[..pos].to_string()
} else {
input.to_string()
}
}
fn eq_step(
matrix_of_names: &[Vec<String>],
eq_i: &Expr,
values: &[String],
arg: &str,
j: usize,
t: f64,
scheme: &str,
) -> Expr {
let vec_of_names_on_step = &matrix_of_names[j];
let hashmap_for_rename: HashMap<String, String> = values
.iter()
.zip(vec_of_names_on_step.iter())
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect();
let eq_step_j = eq_i.rename_variables(&hashmap_for_rename);
let eq_step_j = eq_step_j.set_variable(arg, t);
match scheme {
"forward" => eq_step_j,
"trapezoid" => {
let vec_of_names_on_step = &matrix_of_names[j + 1];
let hashmap_for_rename: HashMap<String, String> = values
.iter()
.zip(vec_of_names_on_step.iter())
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect();
let eq_step_j_plus_1 = eq_i
.rename_variables(&hashmap_for_rename)
.set_variable(arg, t);
Expr::Const(0.5) * (eq_step_j + eq_step_j_plus_1)
}
_ => panic!("Invalid scheme"),
}
}
fn create_mesh(
&self,
n_steps: Option<usize>,
h: Option<f64>,
mesh: Option<Vec<f64>>,
t0: f64,
) -> (Vec<Expr>, Vec<f64>, usize) {
if let Some(mesh) = mesh {
let n_steps = mesh.len();
info!("mesh with n_steps = {} is defined directly", n_steps);
let H: Vec<Expr> = mesh
.windows(2)
.map(|window| Expr::Const(window[1] - window[0]))
.collect();
(H, mesh, n_steps)
} else {
let n_steps = n_steps.unwrap_or(100) + 1;
info!(
"mesh is not defined, creating evenly distributed mesh of length {}",
n_steps
);
let h = h.unwrap_or(1.0);
let H: Vec<Expr> = vec![Expr::Const(h); n_steps - 1]; let T_list: Vec<f64> = (0..n_steps).map(|i| t0 + (i as f64) * h).collect();
(H, T_list, n_steps)
}
}
fn process_bounds_and_tolerances(
&mut self,
Bounds: Option<HashMap<String, (f64, f64)>>,
rel_tolerance: Option<HashMap<String, f64>>,
flat_list_of_names: Vec<String>,
) {
let len = flat_list_of_names.len();
self.bounds = Bounds.as_ref().map(|bounds_map| {
let mut vec_of_bounds = Vec::with_capacity(len);
for name in &flat_list_of_names {
let base_name = if let Some(pos) = name.rfind('_') {
&name[..pos]
} else {
name
};
if let Some(&bound_pair) = bounds_map.get(base_name) {
vec_of_bounds.push(bound_pair);
}
}
vec_of_bounds
});
self.rel_tolerance_vec = rel_tolerance.as_ref().map(|tolerance_map| {
let mut vec_of_tolerance = Vec::with_capacity(len);
for name in &flat_list_of_names {
let base_name = if let Some(pos) = name.rfind('_') {
&name[..pos]
} else {
name
};
if let Some(&tolerance) = tolerance_map.get(base_name) {
vec_of_tolerance.push(tolerance);
}
}
vec_of_tolerance
});
}
pub fn discretization_system_BVP_par(
&mut self,
eq_system: Vec<Expr>,
values: Vec<String>,
arg: String,
t0: f64,
n_steps: Option<usize>,
h: Option<f64>,
mesh: Option<Vec<f64>>,
BorderConditions: HashMap<String, Vec<(usize, f64)>>,
Bounds: Option<HashMap<String, (f64, f64)>>,
rel_tolerance: Option<HashMap<String, f64>>,
scheme: String,
) {
let total_start = Instant::now();
let mut timer_hash: HashMap<String, f64> = HashMap::new();
let (H, T_list, n_steps) = self.create_mesh(n_steps, h, mesh, t0);
let (matrix_of_expr, matrix_of_names) = Expr::IndexedVarsMatrix(n_steps, values.clone());
let bc_handling = Instant::now();
let bc_lookup: HashMap<String, HashMap<usize, f64>> = BorderConditions
.into_iter()
.map(|(k, v)| (k, v.into_iter().collect()))
.collect();
let mut vars_for_boundary_conditions = HashMap::new();
let mut vars_to_exclude = HashSet::new();
let mut bc_pos_n_values = Vec::new();
for (var_name, conditions) in &bc_lookup {
if let Some(var_idx) = values.iter().position(|v| v == var_name) {
for (&pos, &value) in conditions {
match pos {
0 => {
let var_name = &matrix_of_names[0][var_idx];
vars_for_boundary_conditions.insert(var_name.clone(), value);
vars_to_exclude.insert(var_name.clone());
let full_pos = 0 * values.len() + var_idx;
bc_pos_n_values.push((full_pos, 0 as usize, value));
}
1 => {
let var_name = &matrix_of_names[n_steps - 1][var_idx];
vars_for_boundary_conditions.insert(var_name.clone(), value);
vars_to_exclude.insert(var_name.clone());
let full_pos = (n_steps - 1) * values.len() + var_idx;
bc_pos_n_values.push((full_pos, 1 as usize, value));
}
_ => {}
}
}
}
}
timer_hash.insert(
"bc handling".to_string(),
bc_handling.elapsed().as_millis() as f64,
);
println!("creating discretization equations");
let discretization_start = Instant::now();
let (discreditized_system, variables_for_all_discrete): (Vec<Expr>, Vec<Vec<String>>) = (0
..n_steps - 1)
.into_par_iter()
.flat_map(|j| {
let t = T_list[j];
eq_system
.par_iter()
.enumerate()
.map(|(i, eq_i)| {
let eq_step_j =
Self::eq_step(&matrix_of_names, eq_i, &values, &arg, j, t, &scheme);
let Y_j_plus_1 = &matrix_of_expr[j + 1][i];
let Y_j = &matrix_of_expr[j][i];
let res_ij = Y_j_plus_1.clone() - Y_j.clone() - H[j].clone() * eq_step_j;
let mut vars_in_equation = Vec::new();
let y_j_plus_1_name = &matrix_of_names[j + 1][i];
let y_j_name = &matrix_of_names[j][i];
if !vars_to_exclude.contains(y_j_plus_1_name) {
vars_in_equation.push(y_j_plus_1_name.clone());
}
if !vars_to_exclude.contains(y_j_name) {
vars_in_equation.push(y_j_name.clone());
}
for var_idx in 0..values.len() {
let var_name = &matrix_of_names[j][var_idx];
if !vars_to_exclude.contains(var_name) {
vars_in_equation.push(var_name.clone());
}
}
(res_ij.simplify(), vars_in_equation)
})
.collect::<Vec<_>>()
})
.unzip();
self.variables_for_all_disrete = variables_for_all_discrete;
timer_hash.insert(
"discretization of equations".to_string(),
discretization_start.elapsed().as_millis() as f64,
);
let start_flat_list = Instant::now();
let total_vars = values.len() * n_steps;
let mut flat_list_of_names = Vec::with_capacity(total_vars);
let mut flat_list_of_expr = Vec::with_capacity(total_vars);
for time_idx in 0..n_steps {
for var_idx in 0..values.len() {
let name = &matrix_of_names[time_idx][var_idx];
if !vars_to_exclude.contains(name) {
flat_list_of_names.push(name.clone());
flat_list_of_expr.push(matrix_of_expr[time_idx][var_idx].clone());
}
}
}
timer_hash.insert(
"flat list creation".to_string(),
start_flat_list.elapsed().as_millis() as f64,
);
let BC_application_start = Instant::now();
let discreditized_system_with_BC: Vec<Expr> = discreditized_system
.into_par_iter()
.map(|mut eq_i| {
for (var_name, &value) in &vars_for_boundary_conditions {
eq_i = eq_i.set_variable(var_name, value);
}
eq_i.simplify()
})
.collect();
let discreditized_system_flat = discreditized_system_with_BC;
timer_hash.insert(
"BC application".to_string(),
BC_application_start.elapsed().as_millis() as f64,
);
let consistency_start = Instant::now();
let hashset_of_vars: HashSet<&String> = flat_list_of_names.iter().collect();
let mut missing_vars = Vec::new();
for var_list in &self.variables_for_all_disrete {
for var in var_list {
if !hashset_of_vars.contains(var) {
missing_vars.push(var.clone());
}
}
}
if !missing_vars.is_empty() {
missing_vars.sort_unstable();
missing_vars.dedup();
panic!("Variables not found in system: {:?}", missing_vars);
}
timer_hash.insert(
"consistency test".to_string(),
consistency_start.elapsed().as_millis() as f64,
);
self.vector_of_functions = discreditized_system_flat;
self.vector_of_variables = flat_list_of_expr;
self.variable_string = flat_list_of_names.clone();
self.BC_pos_n_values = bc_pos_n_values;
let bounds_and_tolerances_start = Instant::now();
self.process_bounds_and_tolerances(Bounds, rel_tolerance, flat_list_of_names);
timer_hash.insert(
"bounds and tolerances".to_string(),
bounds_and_tolerances_start.elapsed().as_millis() as f64,
);
let total_end = total_start.elapsed().as_millis() as f64;
*timer_hash.get_mut("bc handling").unwrap() /= total_end / 100.0;
*timer_hash.get_mut("discretization of equations").unwrap() /= total_end / 100.0;
*timer_hash.get_mut("BC application").unwrap() /= total_end / 100.0;
*timer_hash.get_mut("flat list creation").unwrap() /= total_end / 100.0;
*timer_hash.get_mut("consistency test").unwrap() /= total_end / 100.0;
*timer_hash.get_mut("bounds and tolerances").unwrap() /= total_end / 100.0;
timer_hash.insert("total time, sec".to_string(), total_end);
let mut table = Builder::from(timer_hash.clone()).build();
table.with(Style::modern_rounded());
println!("{}", table.to_string());
}
fn find_bandwidths(&mut self) {
let A = &self.symbolic_jacobian;
let n = A.len();
let (kl, ku) = (0..n)
.into_par_iter()
.map(|i| {
let mut row_kl = 0;
let mut row_ku = 0;
for j in 0..n {
if A[i][j] != Expr::Const(0.0) {
if j > i {
row_ku = std::cmp::max(row_ku, j - i);
} else if i > j {
row_kl = std::cmp::max(row_kl, i - j);
}
}
}
(row_kl, row_ku)
})
.reduce(
|| (0, 0),
|acc, row| (std::cmp::max(acc.0, row.0), std::cmp::max(acc.1, row.1)),
);
self.bandwidth = Some((kl, ku));
}
pub fn generate_BVP(
&mut self,
eq_system: Vec<Expr>,
values: Vec<String>,
arg: String,
t0: f64,
param: Option<String>,
n_steps: Option<usize>,
h: Option<f64>,
mesh: Option<Vec<f64>>,
BorderConditions: HashMap<String, Vec<(usize, f64)>>,
Bounds: Option<HashMap<String, (f64, f64)>>,
rel_tolerance: Option<HashMap<String, f64>>,
scheme: String,
method: String,
bandwidth: Option<(usize, usize)>,
) {
let total_start = Instant::now();
let mut timer_hash: HashMap<String, f64> = HashMap::new();
let param = if let Some(param) = param {
param
} else {
arg.clone()
};
self.method = method.clone();
let begin = Instant::now();
self.discretization_system_BVP_par(
eq_system,
values.clone(),
arg.clone(),
t0,
n_steps,
h,
mesh,
BorderConditions,
Bounds,
rel_tolerance,
scheme,
);
timer_hash.insert(
"discretization time".to_string(),
begin.elapsed().as_secs_f64(),
);
info!("system discretized");
let v = self.variable_string.clone();
let indexed_values: Vec<&str> = v.iter().map(|x| x.as_str()).collect();
if let Some(bandwidth_) = bandwidth {
let now = Instant::now();
self.bandwidth = Some(bandwidth_);
info!("Bandwidth provided: {:?}", self.bandwidth);
timer_hash.insert(
"find bandwidth time".to_string(),
now.elapsed().as_secs_f64(),
);
info!("Calculating jacobian");
let now = Instant::now();
self.calc_jacobian_parallel_smart_optimized_with_given_bandwidth(); info!("Jacobian calculation time:");
let elapsed = now.elapsed();
info!("{:?}", elapsed_time(elapsed));
timer_hash.insert("symbolic jacobian time".to_string(), elapsed.as_secs_f64());
} else {
info!("Calculating jacobian");
let now = Instant::now();
self.calc_jacobian_parallel_smart_optimized(); info!("Jacobian calculation time:");
let elapsed = now.elapsed();
info!("{:?}", elapsed_time(elapsed));
timer_hash.insert("symbolic jacobian time".to_string(), elapsed.as_secs_f64());
let now = Instant::now();
self.find_bandwidths(); info!("Bandwidth calculated:");
info!("(kl, ku) = {:?}", self.bandwidth);
timer_hash.insert(
"find bandwidth time".to_string(),
now.elapsed().as_secs_f64(),
);
}
let now = Instant::now();
match method.as_str() {
"Dense" => self.lambdify_jacobian_DMatrix_par(param.as_str(), indexed_values.clone()),
"Sparse_1" => self.lambdify_jacobian_CsMat(param.as_str(), indexed_values.clone()),
"Sparse_2" => self.lambdify_jacobian_CsMatrix(param.as_str(), indexed_values.clone()),
"Sparse" => self
.lambdify_jacobian_SparseColMat_parallel2(param.as_str(), indexed_values.clone()),
_ => panic!("unknown method: {}", method),
}
timer_hash.insert(
"jacobian lambdify time".to_string(),
now.elapsed().as_secs_f64(),
);
info!("Jacobian lambdified");
let n = &self.symbolic_jacobian.len();
for (_i, vec_s) in self.symbolic_jacobian.iter().enumerate() {
assert_eq!(vec_s.len(), *n, "jacobian not square ");
}
let now = Instant::now();
match method.as_str() {
"Dense" => self.lambdify_residual_DVector(param.as_str(), indexed_values.clone()),
"Sparse_1" => self.lambdify_residual_CsVec(param.as_str(), indexed_values.clone()),
"Sparse_2" => self.lambdify_residual_DVector(param.as_str(), indexed_values.clone()),
"Sparse" => {
self.lambdify_residual_Col_parallel2(param.as_str(), indexed_values.clone())
}
_ => panic!("unknown method: {}", method),
}
timer_hash.insert(
"residual functions lambdify time".to_string(),
now.elapsed().as_secs_f64(),
);
info!("Residuals vector lambdified");
let total_end = total_start.elapsed().as_secs_f64();
*timer_hash.get_mut("discretization time").unwrap() /= total_end / 100.0;
*timer_hash.get_mut("symbolic jacobian time").unwrap() /= total_end / 100.0;
*timer_hash.get_mut("jacobian lambdify time").unwrap() /= total_end / 100.0;
*timer_hash
.get_mut("residual functions lambdify time")
.unwrap() /= total_end / 100.0;
*timer_hash.get_mut("find bandwidth time").unwrap() /= total_end / 100.0;
timer_hash.insert("total time, sec".to_string(), total_end);
let mut table = Builder::from(timer_hash.clone()).build();
table.with(Style::modern_rounded());
println!("{}", table.to_string());
info!(
"\n \n ____________END OF GENERATE BVP ________________________________________________________________"
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::numerical::BVP_Damp::BVP_traits::Vectors_type_casting;
#[test]
fn generate_Jac_test() {
let Jacobian_instance = &mut Jacobian::new();
let y = Expr::Var("y".to_string());
let z: Expr = Expr::Var("z".to_string());
let eq1: Expr = z.clone() * Expr::Const(10.0) + Expr::exp(y.clone());
let eq2: Expr = Expr::ln(z.clone()) + y.clone();
let eq_system = vec![eq1, eq2];
let values = vec!["y".to_string(), "z".to_string()];
Jacobian_instance.from_vectors(eq_system, values);
Jacobian_instance.calc_jacobian_parallel();
println!(
"Jacobian_instance.evaluated_jacobian_DMatrix = {:?}",
Jacobian_instance.symbolic_jacobian
);
let vect = &DVector::from_vec(vec![0.0, 1.0]);
let expexted_jac = DMatrix::from_row_slice(2, 2, &[1.0, 10.0, 1.0, 1.0]);
Jacobian_instance.lambdify_jacobian_SparseColMat_modified("x", vec!["y", "z"]);
let variables = &*Vectors_type_casting(vect, "Sparse".to_string());
let jac = Jacobian_instance.jac_function.as_mut().unwrap();
let result_SparseColMat = jac.call(1.0, variables);
assert_eq!(
result_SparseColMat.to_DMatrixType(),
expexted_jac,
"FAER CRATE jac error"
);
Jacobian_instance.lambdify_jacobian_DMatrix_par("x", vec!["y", "z"]);
let variables = &*Vectors_type_casting(vect, "Dense".to_string());
let jac = Jacobian_instance.jac_function.as_mut().unwrap();
let result_DMatrix = jac.call(1.0, variables);
assert_eq!(
result_DMatrix.to_DMatrixType(),
expexted_jac,
"nalagebra jac error"
);
Jacobian_instance.lambdify_jacobian_CsMatrix("x", vec!["y", "z"]);
let variables = &*Vectors_type_casting(vect, "Sparse_2".to_string());
let jac = Jacobian_instance.jac_function.as_mut().unwrap();
let result_CsMatrix = jac.call(1.0, variables);
assert_eq!(
result_CsMatrix.to_DMatrixType(),
expexted_jac,
"sprs jac error"
);
}
#[test]
fn CsMat_test() {}
}