use crate::space_3d::Vector3D; use crate::clue_errors::*; use lebedev_laikov; use rand_chacha::ChaCha20Rng; /// `IntegrationGrid` is a struct for use in /// [quadrature](https://en.wikipedia.org/wiki/Quadrature_(mathematics)). #[derive(Debug, Clone,PartialEq)] pub struct IntegrationGrid{ dim: usize, points: Vec::, weights: Vec::, } impl IntegrationGrid{ /// This function generates a /// [Lebedev](https://en.wikipedia.org/wiki/Lebedev_quadrature) /// quadrature grid as an `IntegrationGrid`. /// It uses the /// [lebedev_laikov crate](https://github.com/Rufflewind/lebedev_laikov) /// by [Rufflewind](https://github.com/Rufflewind). /// # Example /// ``` ///# use clue_oxide::integration_grid::IntegrationGrid; /// let n = 26; /// let grid = IntegrationGrid::lebedev(n); /// ``` /// /// # Panic /// `lebedev()` will paninc if /// `n` ∉ \{6, 14, 26, 38, 50, 74, 86, 110, 146, 170, /// 194, 230, 266, 302, 350, 434, 590, 770, 974, 1202, /// 1454, 1730, 2030, 2354, 2702, 3074, 3470, 3890, 4334, 4802, /// 5294, 5810\}. /// pub fn lebedev(n: usize) -> Result{ const LEBEDEVGRIDSIZES: [usize;32] = [ 6, 14, 26, 38, 50, 74, 86, 110, 146, 170, 194, 230, 266, 302, 350, 434, 590, 770, 974, 1202, 1454, 1730, 2030, 2354, 2702, 3074, 3470, 3890, 4334, 4802, 5294, 5810]; if !LEBEDEVGRIDSIZES.contains(&n){ return Err(CluEError::NotALebedevGrid(n)); } let (x, y, z, weights) = lebedev_laikov::ld_vecs(n); let dim = 3; let n_elements = n*dim; let mut points = Vec::::with_capacity(n_elements); for ((xi,yi),zi) in x.iter().zip(y.iter()).zip(z.iter()){ points.push(*xi); points.push(*yi); points.push(*zi); } Ok(IntegrationGrid{dim,points,weights}) } //---------------------------------------------------------------------------- /// This function generates a single point grid one unit in the z-direction. pub fn z_3d() -> Self{ let dim = 3; let points = vec![0.0, 0.0, 1.0]; let weights = vec![1.0]; IntegrationGrid{dim,points,weights} } //---------------------------------------------------------------------------- /// This function generates a new `dim`-dimensional grid with no points. pub fn new(dim: usize) -> Self { let points = Vec::::new(); let weights = Vec::::new(); IntegrationGrid{dim,points,weights} } //---------------------------------------------------------------------------- /// This function generations a grid with `num_point` points randomly /// distributed over the unit sphere. pub fn random_unit_sphere(num_points: usize,rng: &mut ChaCha20Rng) -> Self { let dim = 3; let mut points = Vec::::with_capacity(dim*num_points); let w = 1.0/(num_points as f64); let weights = (0..num_points).map(|_| w).collect::>(); for _ii in 0..num_points{ let r = Vector3D::random_direction(rng); points.push(r.x()); points.push(r.y()); points.push(r.z()); } IntegrationGrid{dim,points,weights} } //---------------------------------------------------------------------------- /// This function scales all the points by a `scale_factor`. pub fn scale(&self, scale_factor: f64) -> Self{ let points = self.points.iter().map(|x| x*scale_factor) .collect::>(); IntegrationGrid{dim: self.dim ,points,weights: self.weights.clone()} } //---------------------------------------------------------------------------- /// This function pushes a new point to a grid. pub fn push(&mut self, points: Vec::,weight: f64) -> Result<(),CluEError> { if points.len() != self.dim{ return Err(CluEError::CannotAddPointToGrid(points.len(), self.dim)); } for x in points.into_iter(){ self.points.push(x); } self.weights.push(weight); Ok(()) } //---------------------------------------------------------------------------- /// This function returns the number of points in a grid. pub fn len(&self) -> usize { self.weights.len() } //---------------------------------------------------------------------------- /// This function returns the dimensionality of the space that the grid /// is imbeded in. pub fn dim(&self) -> usize { self.dim } //---------------------------------------------------------------------------- /// This function checks if the grid is empty. pub fn is_empty(&self) -> bool { self.weights.is_empty() } //---------------------------------------------------------------------------- /// This function returns the weight of point `index`. pub fn weight(&self, index: usize) -> f64{ self.weights[index] } //---------------------------------------------------------------------------- /// This function returns the x value of point `index`. pub fn x(&self, index: usize) -> f64{ let idx = self.dim*index; self.points[idx] } //---------------------------------------------------------------------------- /// This function returns the y value of point `index`. /// The function will panic if the space is not at least 2D. pub fn y(&self, index: usize) -> f64{ assert!(self.dim >= 2); let idx = self.dim*index; self.points[idx+1] } //---------------------------------------------------------------------------- /// This function returns the z value of point `index`. /// The function will panic if the space is not at least 3D. pub fn z(&self, index: usize) -> f64{ assert!(self.dim >= 3); let idx = self.dim*index; self.points[idx+2] } //---------------------------------------------------------------------------- /// This function returns the coordinates of point `index`. /// The function will err if the space is not 3D. pub fn xyz(&self, index: usize) -> Result{ if self.dim != 3{ return Err(CluEError::NotA3DVector(self.dim)); } Ok(Vector3D::from( [ self.x(index), self.y(index), self.z(index) ] )) } //---------------------------------------------------------------------------- /// This function returns the centroid of the grid. pub fn mean(&self) -> Vec::{ let mut r: Vec:: = (0..self.dim).map(|_| 0.0).collect(); for (ii,x) in self.points.iter().enumerate(){ let idx = ii%self.dim; r[idx] += x; } let normalizing_factor = (self.dim as f64)/(self.points.len() as f64); r = r.iter().map(|x| normalizing_factor*x).collect(); r } //---------------------------------------------------------------------------- /// This function translates all the points in the grid by `vector`. /// The function will panic if the vector is of a different dimension than /// the grid. pub fn translate(&mut self, vector: &[f64]){ assert_eq!(vector.len(), self.dim); for (ii,x) in self.points.iter_mut().enumerate(){ let idx = ii%self.dim; *x += vector[idx]; } } //---------------------------------------------------------------------------- /// This function reads a grid from a csv file. pub fn read_from_csv(filename: &str) -> Result{ let Ok(mut rdr) = csv::Reader::from_path(filename) else{ return Err(CluEError::CannotOpenFile(filename.to_string())); }; let mut num_rows = 0; let mut num_cols = 0; for result in rdr.records() { if let Ok(str_rec) = result{ num_cols = str_rec.len(); }else{ return Err(CluEError::CannotOpenFile(filename.to_string())); }; num_rows += 1; } if num_cols <= 1{ return Err(CluEError::CannotReadGrid(filename.to_string())); } let dim = num_cols - 1; let mut points = Vec::::with_capacity(dim*num_rows); let mut weights = Vec::::with_capacity(num_rows); let Ok(mut rdr) = csv::Reader::from_path(filename) else{ return Err(CluEError::CannotOpenFile(filename.to_string())); }; for result in rdr.records() { let Ok(record) = result else{ return Err(CluEError::CannotOpenFile(filename.to_string())); }; for (icol,entry) in record.iter().enumerate(){ let Ok(v) = entry.parse::() else{ return Err(CluEError::CannotOpenFile(filename.to_string())); }; if icol < dim{ points.push(v); }else{ weights.push(v); } } } if points.len() != dim*num_rows || weights.len() != num_rows{ return Err(CluEError::CannotReadGrid(filename.to_string())); } Ok(IntegrationGrid{dim,points,weights}) } //---------------------------------------------------------------------------- /// This function removes the negative hemisphere of a grid. pub fn remove_3d_hemisphere(self) -> IntegrationGrid{ assert_eq!(self.dim,3); let mut points = Vec::::with_capacity(self.points.len()/2); let mut weights = Vec::::with_capacity(self.len()/2); let thr = 1e-12; for ii in 0..self.len(){ let x = self.x(ii); let y = self.y(ii); let z = self.z(ii); if (z < 0.0) || (z < thr && y < 0.0) || (z < thr && y < thr && x < 0.0){ continue; } points.push(x); points.push(y); points.push(z); weights.push(self.weight(ii)); } let norm: f64 = weights.iter().sum(); IntegrationGrid{ dim: self.dim, points, weights: weights.iter().map(|x| x/norm).collect() } } //---------------------------------------------------------------------------- } #[cfg(test)] mod tests{ use super::*; //---------------------------------------------------------------------------- #[test] fn test_read_from_csv(){ let grid = IntegrationGrid::read_from_csv("assets/xyzw.csv").unwrap(); assert_eq!(grid.dim, 3); assert_eq!(grid.points, vec![1.0,0.0,0.0, 0.0,1.0,0.0, 0.0,0.0,1.0]); assert_eq!(grid.weights, vec![0.3333,0.3333,0.3333]); } //---------------------------------------------------------------------------- #[test] fn test_lebedev(){ let grid = IntegrationGrid::lebedev(6).unwrap(); let norm: f64 = grid.weights.iter().sum(); assert!( (norm- 1.0).abs() <1e12); assert_eq!(grid.points, vec![ 1.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, -1.0, ]); assert_eq!(grid.mean(), vec![0.0,0.0,0.0]); } //---------------------------------------------------------------------------- #[test] fn test_remove_3d_hemisphere(){ let grid = IntegrationGrid::lebedev(6).expect("not a Lebedev grid") .remove_3d_hemisphere(); let norm: f64 = grid.weights.iter().sum(); assert!( (norm- 1.0).abs() <1e12); assert_eq!(grid.points, vec![ 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, ]); assert_eq!(grid.mean(), vec![1.0/3.0,1.0/3.0,1.0/3.0]); } //---------------------------------------------------------------------------- #[test] fn test_translate(){ let mut grid = IntegrationGrid::new(3); let w = 1.0/6.0; grid.push(vec![1.0, 2.0, 2.0],w).unwrap(); grid.push(vec![3.0, 2.0, 2.0],w).unwrap(); grid.push(vec![2.0, 1.0, 2.0],w).unwrap(); grid.push(vec![2.0, 3.0, 2.0],w).unwrap(); grid.push(vec![2.0, 2.0, 1.0],w).unwrap(); grid.push(vec![2.0, 2.0, 3.0],w).unwrap(); assert_eq!(grid.mean(), vec![2.0, 2.0, 2.0]); let r = vec![-2.0,-2.0,-2.0]; grid.translate(&r); assert_eq!(grid.mean(), vec![0.0, 0.0, 0.0]); } }