1use feos_core::ReferenceSystem;
2use ndarray::{Array1, Array2};
3use quantity::{Angle, Length, Quantity};
4use std::f64::consts::{FRAC_PI_3, PI};
5
6#[derive(Clone)]
8pub enum Grid {
9 Cartesian1(Axis),
10 Cartesian2(Axis, Axis),
11 Periodical2(Axis, Axis, Angle),
12 Cartesian3(Axis, Axis, Axis),
13 Periodical3(Axis, Axis, Axis, [Angle; 3]),
14 Spherical(Axis),
15 Polar(Axis),
16 Cylindrical { r: Axis, z: Axis },
17}
18
19impl Grid {
20 pub fn new_1d(axis: Axis) -> Self {
21 match axis.geometry {
22 Geometry::Cartesian => Self::Cartesian1(axis),
23 Geometry::Cylindrical => Self::Polar(axis),
24 Geometry::Spherical => Self::Spherical(axis),
25 }
26 }
27
28 pub fn axes(&self) -> Vec<&Axis> {
29 match self {
30 Self::Cartesian1(x) => vec![x],
31 Self::Cartesian2(x, y) | Self::Periodical2(x, y, _) => vec![x, y],
32 Self::Cartesian3(x, y, z) | Self::Periodical3(x, y, z, _) => vec![x, y, z],
33 Self::Spherical(r) | Self::Polar(r) => vec![r],
34 Self::Cylindrical { r, z } => vec![r, z],
35 }
36 }
37
38 pub fn axes_mut(&mut self) -> Vec<&mut Axis> {
39 match self {
40 Self::Cartesian1(x) => vec![x],
41 Self::Cartesian2(x, y) | Self::Periodical2(x, y, _) => vec![x, y],
42 Self::Cartesian3(x, y, z) | Self::Periodical3(x, y, z, _) => vec![x, y, z],
43 Self::Spherical(r) | Self::Polar(r) => vec![r],
44 Self::Cylindrical { r, z } => vec![r, z],
45 }
46 }
47
48 pub fn grids(&self) -> Vec<&Array1<f64>> {
49 self.axes().iter().map(|ax| &ax.grid).collect()
50 }
51
52 pub(crate) fn integration_weights(&self) -> (Vec<&Array1<f64>>, f64) {
53 (
54 self.axes()
55 .iter()
56 .map(|ax| &ax.integration_weights)
57 .collect(),
58 self.functional_determinant(),
59 )
60 }
61
62 pub(crate) fn functional_determinant(&self) -> f64 {
63 match &self {
64 Self::Periodical2(_, _, alpha) => alpha.sin(),
65 Self::Periodical3(_, _, _, [alpha, beta, gamma]) => {
66 let xi = (alpha.cos() - gamma.cos() * beta.cos()) / gamma.sin();
67 gamma.sin() * (1.0 - beta.cos().powi(2) - xi * xi).sqrt()
68 }
69 _ => 1.0,
70 }
71 }
72}
73
74#[derive(Copy, Clone, PartialEq)]
76#[cfg_attr(feature = "python", pyo3::pyclass(eq))]
77pub enum Geometry {
78 Cartesian,
79 Cylindrical,
80 Spherical,
81}
82
83impl Geometry {
84 pub fn dimension(&self) -> i32 {
86 match self {
87 Self::Cartesian => 1,
88 Self::Cylindrical => 2,
89 Self::Spherical => 3,
90 }
91 }
92}
93
94#[derive(Clone)]
96pub struct Axis {
97 pub geometry: Geometry,
98 pub grid: Array1<f64>,
99 pub edges: Array1<f64>,
100 integration_weights: Array1<f64>,
101 potential_offset: f64,
102}
103
104impl Axis {
105 pub fn new_cartesian(points: usize, length: Length, potential_offset: Option<f64>) -> Self {
110 let potential_offset = potential_offset.unwrap_or(0.0);
111 let l = length.to_reduced() + potential_offset;
112 let cell_size = l / points as f64;
113 let grid = Array1::linspace(0.5 * cell_size, l - 0.5 * cell_size, points);
114 let edges = Array1::linspace(0.0, l, points + 1);
115 let integration_weights = Array1::from_elem(points, cell_size);
116 Self {
117 geometry: Geometry::Cartesian,
118 grid,
119 edges,
120 integration_weights,
121 potential_offset,
122 }
123 }
124
125 pub fn new_spherical(points: usize, length: Length) -> Self {
127 let l = length.to_reduced();
128 let cell_size = l / points as f64;
129 let grid = Array1::linspace(0.5 * cell_size, l - 0.5 * cell_size, points);
130 let edges = Array1::linspace(0.0, l, points + 1);
131 let integration_weights = Array1::from_shape_fn(points, |k| {
132 4.0 * FRAC_PI_3 * cell_size.powi(3) * (3 * k * k + 3 * k + 1) as f64
133 });
134 Self {
135 geometry: Geometry::Spherical,
136 grid,
137 edges,
138 integration_weights,
139 potential_offset: 0.0,
140 }
141 }
142
143 pub fn new_polar(points: usize, length: Length) -> Self {
145 let l = length.to_reduced();
146
147 let mut alpha = 0.002_f64;
148 for _ in 0..20 {
149 alpha = -(1.0 - (-alpha).exp()).ln() / (points - 1) as f64;
150 }
151 let x0 = 0.5 * ((-alpha * points as f64).exp() + (-alpha * (points - 1) as f64).exp());
152 let grid = (0..points)
153 .map(|i| l * x0 * (alpha * i as f64).exp())
154 .collect();
155 let edges = (0..=points)
156 .map(|i| {
157 if i == 0 {
158 0.0
159 } else {
160 l * (-alpha * (points - i) as f64).exp()
161 }
162 })
163 .collect();
164
165 let k0 = (2.0 * alpha).exp() * (2.0 * alpha.exp() + (2.0 * alpha).exp() - 1.0)
166 / ((1.0 + alpha.exp()).powi(2) * ((2.0 * alpha).exp() - 1.0));
167 let integration_weights = (0..points)
168 .map(|i| {
169 (match i {
170 0 => k0 * (2.0 * alpha).exp(),
171 1 => ((2.0 * alpha).exp() - k0) * (2.0 * alpha).exp(),
172 _ => (2.0 * alpha * i as f64).exp() * ((2.0 * alpha).exp() - 1.0),
173 }) * ((-2.0 * alpha * points as f64).exp() * PI * l * l)
174 })
175 .collect();
176
177 Self {
178 geometry: Geometry::Cylindrical,
179 grid,
180 edges,
181 integration_weights,
182 potential_offset: 0.0,
183 }
184 }
185
186 pub fn length(&self) -> f64 {
191 self.edges[self.grid.len()] - self.edges[0]
192 }
193
194 pub fn volume(&self) -> f64 {
200 let length = self.edges[self.grid.len()] - self.potential_offset - self.edges[0];
201 (match self.geometry {
202 Geometry::Cartesian => 1.0,
203 Geometry::Cylindrical => 4.0 * PI,
204 Geometry::Spherical => 4.0 * FRAC_PI_3,
205 }) * length.powi(self.geometry.dimension())
206 }
207
208 pub fn interpolate<U>(
210 &self,
211 x: f64,
212 y: &Quantity<Array2<f64>, U>,
213 i: usize,
214 ) -> Quantity<f64, U> {
215 let n = self.grid.len();
216 y.get((
217 i,
218 if x >= self.edges[n] {
219 n - 1
220 } else {
221 match self.geometry {
222 Geometry::Cartesian | Geometry::Spherical => (x / self.edges[1]) as usize,
223 Geometry::Cylindrical => {
224 if x < self.edges[1] {
225 0
226 } else {
227 (n as f64
228 - (n - 1) as f64 * (x / self.edges[n]).ln()
229 / (self.edges[1] / self.edges[n]).ln())
230 as usize
231 }
232 }
233 }
234 },
235 ))
236 }
237}