1use crate::grid::Grid1D;
8use numra_core::Scalar;
9
10#[derive(Clone, Copy, Debug, PartialEq)]
12pub enum DifferenceScheme {
13 Central,
15 Forward,
17 Backward,
19 Central4,
21}
22
23#[derive(Clone, Debug)]
25pub struct Stencil<S: Scalar> {
26 pub coeffs: Vec<S>,
28 pub offset: i32,
30}
31
32impl<S: Scalar> Stencil<S> {
33 pub fn d1_central(dx: S) -> Self {
35 let inv_2dx = S::ONE / (S::from_f64(2.0) * dx);
36 Self {
37 coeffs: vec![-inv_2dx, S::ZERO, inv_2dx],
38 offset: -1,
39 }
40 }
41
42 pub fn d1_forward(dx: S) -> Self {
44 let inv_dx = S::ONE / dx;
45 Self {
46 coeffs: vec![-inv_dx, inv_dx],
47 offset: 0,
48 }
49 }
50
51 pub fn d1_backward(dx: S) -> Self {
53 let inv_dx = S::ONE / dx;
54 Self {
55 coeffs: vec![-inv_dx, inv_dx],
56 offset: -1,
57 }
58 }
59
60 pub fn d2_central(dx: S) -> Self {
62 let inv_dx2 = S::ONE / (dx * dx);
63 Self {
64 coeffs: vec![inv_dx2, -S::from_f64(2.0) * inv_dx2, inv_dx2],
65 offset: -1,
66 }
67 }
68
69 pub fn d2_central4(dx: S) -> Self {
71 let inv_dx2 = S::ONE / (dx * dx);
72 let c = inv_dx2 / S::from_f64(12.0);
73 Self {
74 coeffs: vec![
75 -c,
76 S::from_f64(16.0) * c,
77 -S::from_f64(30.0) * c,
78 S::from_f64(16.0) * c,
79 -c,
80 ],
81 offset: -2,
82 }
83 }
84
85 pub fn apply(&self, u: &[S], i: usize) -> S {
87 let mut result = S::ZERO;
88 for (k, &coeff) in self.coeffs.iter().enumerate() {
89 let idx = (i as i32 + self.offset + k as i32) as usize;
90 result = result + coeff * u[idx];
91 }
92 result
93 }
94}
95
96pub struct FDM;
98
99impl FDM {
100 pub fn d1_central<S: Scalar>(u: &[S], dx: S, i: usize) -> S {
104 let inv_2dx = S::ONE / (S::from_f64(2.0) * dx);
105 (u[i + 1] - u[i - 1]) * inv_2dx
106 }
107
108 pub fn d1_forward<S: Scalar>(u: &[S], dx: S, i: usize) -> S {
112 (u[i + 1] - u[i]) / dx
113 }
114
115 pub fn d1_backward<S: Scalar>(u: &[S], dx: S, i: usize) -> S {
119 (u[i] - u[i - 1]) / dx
120 }
121
122 pub fn d2_central<S: Scalar>(u: &[S], dx: S, i: usize) -> S {
126 let inv_dx2 = S::ONE / (dx * dx);
127 (u[i + 1] - S::from_f64(2.0) * u[i] + u[i - 1]) * inv_dx2
128 }
129
130 pub fn d2_central4<S: Scalar>(u: &[S], dx: S, i: usize) -> S {
134 let inv_12dx2 = S::ONE / (S::from_f64(12.0) * dx * dx);
135 (-u[i + 2] + S::from_f64(16.0) * u[i + 1] - S::from_f64(30.0) * u[i]
136 + S::from_f64(16.0) * u[i - 1]
137 - u[i - 2])
138 * inv_12dx2
139 }
140
141 pub fn d1_left_boundary<S: Scalar>(u: &[S], dx: S) -> S {
145 let inv_2dx = S::ONE / (S::from_f64(2.0) * dx);
146 (-S::from_f64(3.0) * u[0] + S::from_f64(4.0) * u[1] - u[2]) * inv_2dx
147 }
148
149 pub fn d1_right_boundary<S: Scalar>(u: &[S], dx: S) -> S {
153 let n = u.len();
154 let inv_2dx = S::ONE / (S::from_f64(2.0) * dx);
155 (S::from_f64(3.0) * u[n - 1] - S::from_f64(4.0) * u[n - 2] + u[n - 3]) * inv_2dx
156 }
157
158 pub fn laplacian_1d<S: Scalar>(u: &[S], dx: S) -> Vec<S> {
162 let n = u.len();
163 let inv_dx2 = S::ONE / (dx * dx);
164 let two = S::from_f64(2.0);
165
166 (1..n - 1)
167 .map(|i| (u[i + 1] - two * u[i] + u[i - 1]) * inv_dx2)
168 .collect()
169 }
170
171 pub fn laplacian_2d<S: Scalar>(u: &[S], nx: usize, ny: usize, dx: S, dy: S) -> Vec<S> {
175 let inv_dx2 = S::ONE / (dx * dx);
176 let inv_dy2 = S::ONE / (dy * dy);
177 let two = S::from_f64(2.0);
178
179 let mut result = vec![S::ZERO; (nx - 2) * (ny - 2)];
180
181 for j in 1..ny - 1 {
182 for i in 1..nx - 1 {
183 let idx = j * nx + i;
184 let d2x = (u[idx + 1] - two * u[idx] + u[idx - 1]) * inv_dx2;
185 let d2y = (u[idx + nx] - two * u[idx] + u[idx - nx]) * inv_dy2;
186
187 let result_idx = (j - 1) * (nx - 2) + (i - 1);
188 result[result_idx] = d2x + d2y;
189 }
190 }
191
192 result
193 }
194
195 pub fn gradient_1d<S: Scalar>(u: &[S], dx: S) -> Vec<S> {
197 let n = u.len();
198 let inv_2dx = S::ONE / (S::from_f64(2.0) * dx);
199
200 (1..n - 1)
201 .map(|i| (u[i + 1] - u[i - 1]) * inv_2dx)
202 .collect()
203 }
204
205 pub fn laplacian_matrix_1d<S: Scalar>(n_interior: usize, dx: S) -> Vec<Vec<S>> {
210 let inv_dx2 = S::ONE / (dx * dx);
211 let two = S::from_f64(2.0);
212
213 let mut matrix = vec![vec![S::ZERO; n_interior]; n_interior];
214
215 for i in 0..n_interior {
216 matrix[i][i] = -two * inv_dx2;
217 if i > 0 {
218 matrix[i][i - 1] = inv_dx2;
219 }
220 if i < n_interior - 1 {
221 matrix[i][i + 1] = inv_dx2;
222 }
223 }
224
225 matrix
226 }
227}
228
229#[allow(dead_code)]
233pub struct NonUniformFDM;
234
235#[allow(dead_code)]
236impl NonUniformFDM {
237 pub fn d2<S: Scalar>(u: &[S], grid: &Grid1D<S>, i: usize) -> S {
242 let dx_minus = grid.dx(i - 1);
243 let dx_plus = grid.dx(i);
244 let sum_dx = dx_minus + dx_plus;
245
246 let two = S::from_f64(2.0);
247 two * ((u[i + 1] - u[i]) / dx_plus - (u[i] - u[i - 1]) / dx_minus) / sum_dx
248 }
249
250 pub fn d1<S: Scalar>(u: &[S], grid: &Grid1D<S>, i: usize) -> S {
252 let dx_minus = grid.dx(i - 1);
253 let dx_plus = grid.dx(i);
254 let sum_dx = dx_minus + dx_plus;
255
256 (dx_minus * dx_minus * u[i + 1] + (dx_plus * dx_plus - dx_minus * dx_minus) * u[i]
257 - dx_plus * dx_plus * u[i - 1])
258 / (dx_minus * dx_plus * sum_dx)
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265
266 #[test]
267 fn test_d1_central() {
268 let u = vec![0.0, 1.0, 4.0]; let dx = 1.0;
272 let deriv = FDM::d1_central(&u, dx, 1);
273 assert!((deriv - 2.0).abs() < 1e-10);
274 }
275
276 #[test]
277 fn test_d2_central() {
278 let u = vec![0.0, 1.0, 4.0]; let dx = 1.0;
281 let deriv = FDM::d2_central(&u, dx, 1);
282 assert!((deriv - 2.0).abs() < 1e-10);
283 }
284
285 #[test]
286 fn test_d2_central_sine() {
287 let n = 101;
289 let dx = 0.1;
290 let u: Vec<f64> = (0..n).map(|i| (i as f64 * dx).sin()).collect();
291
292 let i = 50;
294 let x = i as f64 * dx;
295 let d2u = FDM::d2_central(&u, dx, i);
296 let exact = -(x).sin();
297 assert!(
298 (d2u - exact).abs() < 0.001,
299 "d2u = {}, exact = {}",
300 d2u,
301 exact
302 );
303 }
304
305 #[test]
306 fn test_boundary_derivatives() {
307 let u = vec![0.0, 1.0, 4.0, 9.0, 16.0]; let dx = 1.0;
311
312 let d1_left = FDM::d1_left_boundary(&u, dx);
313 let d1_right = FDM::d1_right_boundary(&u, dx);
314
315 assert!((d1_left - 0.0).abs() < 0.1, "d1_left = {}", d1_left);
317 assert!((d1_right - 8.0).abs() < 0.1, "d1_right = {}", d1_right);
319 }
320
321 #[test]
322 fn test_laplacian_1d() {
323 let n = 101;
325 let dx = 1.0 / (n as f64 - 1.0);
326 let u: Vec<f64> = (0..n)
327 .map(|i| (std::f64::consts::PI * i as f64 * dx).sin())
328 .collect();
329
330 let lap = FDM::laplacian_1d(&u, dx);
331
332 let i = 50;
334 let x = i as f64 * dx;
335 let exact = -std::f64::consts::PI.powi(2) * (std::f64::consts::PI * x).sin();
336 let computed = lap[i - 1];
337
338 assert!(
339 (computed - exact).abs() < 0.1,
340 "computed = {}, exact = {}",
341 computed,
342 exact
343 );
344 }
345
346 #[test]
347 fn test_stencil() {
348 let dx = 0.1;
349 let stencil = Stencil::d2_central(dx);
350
351 let u = vec![0.0, 0.01, 0.04]; let d2u = stencil.apply(&u, 1);
353 assert!((d2u - 2.0).abs() < 1e-10);
354 }
355
356 #[test]
357 fn test_non_uniform_d2() {
358 let points = vec![0.0, 0.3, 0.8, 1.5];
360 let grid = Grid1D::from_points(points);
361 let u: Vec<f64> = grid.points().iter().map(|x| x * x).collect();
362
363 let d2u = NonUniformFDM::d2(&u, &grid, 1);
364 assert!((d2u - 2.0).abs() < 0.1, "d2u = {}", d2u);
365
366 let d2u = NonUniformFDM::d2(&u, &grid, 2);
367 assert!((d2u - 2.0).abs() < 0.1, "d2u = {}", d2u);
368 }
369}