1use crate::Point;
12use num_complex::Complex64;
13use std::f64::consts::PI;
14
15#[inline]
33pub fn greens_function_3d(r: f64, k: f64) -> Complex64 {
34 if r < 1e-15 {
35 return Complex64::new(f64::INFINITY, 0.0);
37 }
38
39 let kr = k * r;
40 let exp_ikr = Complex64::new(kr.cos(), kr.sin());
41 exp_ikr / (4.0 * PI * r)
42}
43
44#[inline]
52pub fn greens_function_2d(r: f64, k: f64) -> Complex64 {
53 use spec_math::Bessel;
54
55 if r < 1e-15 {
56 return Complex64::new(f64::INFINITY, 0.0);
57 }
58
59 let kr = k * r;
60 let j0 = kr.bessel_jv(0.0);
61 let y0 = kr.bessel_yv(0.0);
62 let h0 = Complex64::new(j0, y0);
63
64 Complex64::new(0.0, 0.25) * h0
65}
66
67pub fn greens_function_gradient_3d(source: &Point, field: &Point, k: f64) -> [Complex64; 3] {
81 let rx = field.x - source.x;
82 let ry = field.y - source.y;
83 let rz = field.z - source.z;
84 let r = (rx * rx + ry * ry + rz * rz).sqrt();
85
86 if r < 1e-15 {
87 return [
88 Complex64::new(f64::INFINITY, 0.0),
89 Complex64::new(f64::INFINITY, 0.0),
90 Complex64::new(f64::INFINITY, 0.0),
91 ];
92 }
93
94 let g = greens_function_3d(r, k);
95 let factor = Complex64::new(-1.0 / r, k) * g;
96
97 [factor * rx / r, factor * ry / r, factor * rz / r]
98}
99
100#[inline]
114pub fn greens_function_normal_derivative_3d(
115 source: &Point,
116 field: &Point,
117 normal: &[f64; 3],
118 k: f64,
119) -> Complex64 {
120 let rx = field.x - source.x;
121 let ry = field.y - source.y;
122 let rz = field.z - source.z;
123 let r = (rx * rx + ry * ry + rz * rz).sqrt();
124
125 if r < 1e-15 {
126 return Complex64::new(f64::INFINITY, 0.0);
127 }
128
129 let g = greens_function_3d(r, k);
130 let r_dot_n = rx * normal[0] + ry * normal[1] + rz * normal[2];
131
132 let factor = Complex64::new(-1.0 / r, k);
134
135 factor * g * r_dot_n / r
136}
137
138#[inline]
146pub fn greens_function_adjoint_derivative_3d(
147 source: &Point,
148 field: &Point,
149 normal_source: &[f64; 3],
150 k: f64,
151) -> Complex64 {
152 let rx = field.x - source.x;
153 let ry = field.y - source.y;
154 let rz = field.z - source.z;
155 let r = (rx * rx + ry * ry + rz * rz).sqrt();
156
157 if r < 1e-15 {
158 return Complex64::new(f64::INFINITY, 0.0);
159 }
160
161 let g = greens_function_3d(r, k);
162 let r_dot_n = rx * normal_source[0] + ry * normal_source[1] + rz * normal_source[2];
163
164 let factor = Complex64::new(1.0 / r, -k);
166
167 factor * g * r_dot_n / r
168}
169
170pub fn greens_function_hypersingular_3d(
177 source: &Point,
178 field: &Point,
179 normal_source: &[f64; 3],
180 normal_field: &[f64; 3],
181 k: f64,
182) -> Complex64 {
183 let rx = field.x - source.x;
184 let ry = field.y - source.y;
185 let rz = field.z - source.z;
186 let r2 = rx * rx + ry * ry + rz * rz;
187 let r = r2.sqrt();
188
189 if r < 1e-15 {
190 return Complex64::new(f64::INFINITY, 0.0);
191 }
192
193 let g = greens_function_3d(r, k);
194 let ik = Complex64::new(0.0, k);
195
196 let r_dot_nx = rx * normal_source[0] + ry * normal_source[1] + rz * normal_source[2];
197 let r_dot_ny = rx * normal_field[0] + ry * normal_field[1] + rz * normal_field[2];
198 let nx_dot_ny = normal_source[0] * normal_field[0]
199 + normal_source[1] * normal_field[1]
200 + normal_source[2] * normal_field[2];
201
202 let coef1 = ik * ik - 3.0 * ik / r + Complex64::new(3.0 / r2, 0.0);
204 let term1 = coef1 * r_dot_nx * r_dot_ny / r2;
205
206 let coef2 = ik - Complex64::new(1.0 / r, 0.0);
208 let term2 = coef2 * nx_dot_ny / r;
209
210 (term1 - term2) * g
211}
212
213pub fn all_kernels_3d(
217 source: &Point,
218 field: &Point,
219 normal_source: &[f64; 3],
220 normal_field: &[f64; 3],
221 k: f64,
222) -> (Complex64, Complex64, Complex64, Complex64) {
223 let rx = field.x - source.x;
224 let ry = field.y - source.y;
225 let rz = field.z - source.z;
226 let r2 = rx * rx + ry * ry + rz * rz;
227 let r = r2.sqrt();
228
229 if r < 1e-15 {
230 let inf = Complex64::new(f64::INFINITY, 0.0);
231 return (inf, inf, inf, inf);
232 }
233
234 let kr = k * r;
235 let exp_ikr = Complex64::new(kr.cos(), kr.sin());
236 let g = exp_ikr / (4.0 * PI * r);
237
238 let r_dot_nx = rx * normal_source[0] + ry * normal_source[1] + rz * normal_source[2];
239 let r_dot_ny = rx * normal_field[0] + ry * normal_field[1] + rz * normal_field[2];
240 let nx_dot_ny = normal_source[0] * normal_field[0]
241 + normal_source[1] * normal_field[1]
242 + normal_source[2] * normal_field[2];
243
244 let ik = Complex64::new(0.0, k);
245
246 let factor_dg = ik - Complex64::new(1.0 / r, 0.0);
248 let dg_dny = factor_dg * g * r_dot_ny / r;
249
250 let dg_dnx = -factor_dg * g * r_dot_nx / r;
252
253 let coef1 = ik * ik - 3.0 * ik / r + Complex64::new(3.0 / r2, 0.0);
255 let term1 = coef1 * r_dot_nx * r_dot_ny / r2;
256 let term2 = factor_dg * nx_dot_ny / r;
257 let d2g = (term1 - term2) * g;
258
259 (g, dg_dny, dg_dnx, d2g)
260}
261
262#[inline]
264pub fn distance(p1: &Point, p2: &Point) -> f64 {
265 p1.distance_to(p2)
266}
267
268#[inline]
270pub fn laplace_greens_function_3d(r: f64) -> f64 {
271 if r < 1e-15 {
272 f64::INFINITY
273 } else {
274 1.0 / (4.0 * PI * r)
275 }
276}
277
278#[inline]
280pub fn laplace_greens_function_2d(r: f64) -> f64 {
281 if r < 1e-15 {
282 f64::INFINITY
283 } else {
284 -r.ln() / (2.0 * PI)
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291
292 const EPSILON: f64 = 1e-10;
293
294 #[test]
295 fn test_greens_function_magnitude() {
296 let r = 2.0;
298 let k = 1.5;
299 let g = greens_function_3d(r, k);
300
301 let expected_magnitude = 1.0 / (4.0 * PI * r);
302 assert!((g.norm() - expected_magnitude).abs() < EPSILON);
303 }
304
305 #[test]
306 fn test_greens_function_k_zero() {
307 let r = 1.5;
309 let g = greens_function_3d(r, 0.0);
310
311 let expected = 1.0 / (4.0 * PI * r);
312 assert!((g.re - expected).abs() < EPSILON);
313 assert!(g.im.abs() < EPSILON);
314 }
315
316 #[test]
317 fn test_normal_derivative_radial() {
318 let source = Point::new_3d(0.0, 0.0, 0.0);
320 let field = Point::new_3d(1.0, 0.0, 0.0);
321 let normal = [1.0, 0.0, 0.0]; let k = 2.0;
324 let dg_dn = greens_function_normal_derivative_3d(&source, &field, &normal, k);
325
326 assert!(dg_dn.norm().is_finite());
328 }
329
330 #[test]
331 fn test_normal_derivative_tangential() {
332 let source = Point::new_3d(0.0, 0.0, 0.0);
334 let field = Point::new_3d(1.0, 0.0, 0.0);
335 let normal = [0.0, 1.0, 0.0]; let k = 2.0;
338 let dg_dn = greens_function_normal_derivative_3d(&source, &field, &normal, k);
339
340 assert!(dg_dn.norm() < EPSILON);
342 }
343
344 #[test]
345 fn test_all_kernels_consistency() {
346 let source = Point::new_3d(0.0, 0.0, 0.0);
347 let field = Point::new_3d(1.0, 0.5, 0.3);
348 let n_source = [0.0, 0.0, 1.0];
349 let n_field = [1.0, 0.0, 0.0];
350 let k = 2.5;
351
352 let (g, dg_dny, dg_dnx, _d2g) = all_kernels_3d(&source, &field, &n_source, &n_field, k);
353
354 let g_single = greens_function_3d(distance(&source, &field), k);
356 let dg_dny_single = greens_function_normal_derivative_3d(&source, &field, &n_field, k);
357 let dg_dnx_single = greens_function_adjoint_derivative_3d(&source, &field, &n_source, k);
358
359 assert!((g - g_single).norm() < EPSILON);
360 assert!((dg_dny - dg_dny_single).norm() < EPSILON);
361 assert!((dg_dnx - dg_dnx_single).norm() < EPSILON);
362 }
363
364 #[test]
365 fn test_laplace_greens() {
366 let r = 2.0;
367
368 let g3d = laplace_greens_function_3d(r);
370 assert!((g3d - 1.0 / (4.0 * PI * r)).abs() < EPSILON);
371
372 let g2d = laplace_greens_function_2d(r);
374 assert!((g2d - (-r.ln() / (2.0 * PI))).abs() < EPSILON);
375 }
376
377 #[test]
378 fn test_gradient() {
379 let source = Point::new_3d(0.0, 0.0, 0.0);
380 let field = Point::new_3d(1.0, 0.0, 0.0);
381 let k = 1.0;
382
383 let grad = greens_function_gradient_3d(&source, &field, k);
384
385 assert!(grad[0].norm() > EPSILON);
388 assert!(grad[1].norm() < EPSILON);
389 assert!(grad[2].norm() < EPSILON);
390 }
391}