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)]
76pub enum Geometry {
77 Cartesian,
78 Cylindrical,
79 Spherical,
80}
81
82impl Geometry {
83 pub fn dimension(&self) -> i32 {
85 match self {
86 Self::Cartesian => 1,
87 Self::Cylindrical => 2,
88 Self::Spherical => 3,
89 }
90 }
91}
92
93#[derive(Clone)]
95pub struct Axis {
96 pub geometry: Geometry,
97 pub grid: Array1<f64>,
98 pub edges: Array1<f64>,
99 integration_weights: Array1<f64>,
100 potential_offset: f64,
101}
102
103impl Axis {
104 pub fn new_cartesian(points: usize, length: Length, potential_offset: Option<f64>) -> Self {
109 let potential_offset = potential_offset.unwrap_or(0.0);
110 let l = length.to_reduced() + potential_offset;
111 let cell_size = l / points as f64;
112 let grid = Array1::linspace(0.5 * cell_size, l - 0.5 * cell_size, points);
113 let edges = Array1::linspace(0.0, l, points + 1);
114 let integration_weights = Array1::from_elem(points, cell_size);
115 Self {
116 geometry: Geometry::Cartesian,
117 grid,
118 edges,
119 integration_weights,
120 potential_offset,
121 }
122 }
123
124 pub fn new_spherical(points: usize, length: Length) -> Self {
126 let l = length.to_reduced();
127 let cell_size = l / points as f64;
128 let grid = Array1::linspace(0.5 * cell_size, l - 0.5 * cell_size, points);
129 let edges = Array1::linspace(0.0, l, points + 1);
130 let integration_weights = Array1::from_shape_fn(points, |k| {
131 4.0 * FRAC_PI_3 * cell_size.powi(3) * (3 * k * k + 3 * k + 1) as f64
132 });
133 Self {
134 geometry: Geometry::Spherical,
135 grid,
136 edges,
137 integration_weights,
138 potential_offset: 0.0,
139 }
140 }
141
142 pub fn new_polar(points: usize, length: Length) -> Self {
144 let l = length.to_reduced();
145
146 let mut alpha = 0.002_f64;
147 for _ in 0..20 {
148 alpha = -(1.0 - (-alpha).exp()).ln() / (points - 1) as f64;
149 }
150 let x0 = 0.5 * ((-alpha * points as f64).exp() + (-alpha * (points - 1) as f64).exp());
151 let grid = (0..points)
152 .map(|i| l * x0 * (alpha * i as f64).exp())
153 .collect();
154 let edges = (0..=points)
155 .map(|i| {
156 if i == 0 {
157 0.0
158 } else {
159 l * (-alpha * (points - i) as f64).exp()
160 }
161 })
162 .collect();
163
164 let k0 = (2.0 * alpha).exp() * (2.0 * alpha.exp() + (2.0 * alpha).exp() - 1.0)
165 / ((1.0 + alpha.exp()).powi(2) * ((2.0 * alpha).exp() - 1.0));
166 let integration_weights = (0..points)
167 .map(|i| {
168 (match i {
169 0 => k0 * (2.0 * alpha).exp(),
170 1 => ((2.0 * alpha).exp() - k0) * (2.0 * alpha).exp(),
171 _ => (2.0 * alpha * i as f64).exp() * ((2.0 * alpha).exp() - 1.0),
172 }) * ((-2.0 * alpha * points as f64).exp() * PI * l * l)
173 })
174 .collect();
175
176 Self {
177 geometry: Geometry::Cylindrical,
178 grid,
179 edges,
180 integration_weights,
181 potential_offset: 0.0,
182 }
183 }
184
185 pub fn length(&self) -> f64 {
190 self.edges[self.grid.len()] - self.edges[0]
191 }
192
193 pub fn volume(&self) -> f64 {
199 let length = self.edges[self.grid.len()] - self.potential_offset - self.edges[0];
200 (match self.geometry {
201 Geometry::Cartesian => 1.0,
202 Geometry::Cylindrical => 4.0 * PI,
203 Geometry::Spherical => 4.0 * FRAC_PI_3,
204 }) * length.powi(self.geometry.dimension())
205 }
206
207 pub fn interpolate<U>(
209 &self,
210 x: f64,
211 y: &Quantity<Array2<f64>, U>,
212 i: usize,
213 ) -> Quantity<f64, U> {
214 let n = self.grid.len();
215 y.get((
216 i,
217 if x >= self.edges[n] {
218 n - 1
219 } else {
220 match self.geometry {
221 Geometry::Cartesian | Geometry::Spherical => (x / self.edges[1]) as usize,
222 Geometry::Cylindrical => {
223 if x < self.edges[1] {
224 0
225 } else {
226 (n as f64
227 - (n - 1) as f64 * (x / self.edges[n]).ln()
228 / (self.edges[1] / self.edges[n]).ln())
229 as usize
230 }
231 }
232 }
233 },
234 ))
235 }
236}