use std::ops::Add;
use std::ops::Mul;
use std::ops::Sub;
use rayon::prelude::*;
use serde::Deserialize;
use serde::Serialize;
use crate::numerical::matrix::Matrix;
use crate::numerical::solve::LinearSolution;
use crate::numerical::solve::solve_linear_system;
#[derive(Clone, Copy, Default, Debug, Serialize, Deserialize)]
pub struct Vector2D {
pub x: f64,
pub y: f64,
}
impl Vector2D {
#[must_use]
pub const fn new(
x: f64,
y: f64,
) -> Self {
Self { x, y }
}
#[must_use]
pub fn norm(&self) -> f64 {
self.x.hypot(self.y)
}
}
impl Add for Vector2D {
type Output = Self;
fn add(
self,
rhs: Self,
) -> Self {
Self {
x: self.x + rhs.x,
y: self.y + rhs.y,
}
}
}
impl Mul<f64> for Vector2D {
type Output = Self;
fn mul(
self,
rhs: f64,
) -> Self {
Self {
x: self.x * rhs,
y: self.y * rhs,
}
}
}
impl Sub for Vector2D {
type Output = Self;
fn sub(
self,
rhs: Self,
) -> Self {
Self {
x: self.x - rhs.x,
y: self.y - rhs.y,
}
}
}
#[derive(Clone, Copy, Default, Debug, Serialize, Deserialize)]
pub struct Vector3D {
pub x: f64,
pub y: f64,
pub z: f64,
}
impl Vector3D {
#[allow(dead_code)]
#[must_use]
pub const fn new(
x: f64,
y: f64,
z: f64,
) -> Self {
Self { x, y, z }
}
#[allow(dead_code)]
#[must_use]
pub fn norm(&self) -> f64 {
self.z
.mul_add(self.z, self.x.mul_add(self.x, self.y * self.y))
.sqrt()
}
}
impl Sub for Vector3D {
type Output = Self;
fn sub(
self,
rhs: Self,
) -> Self {
Self {
x: self.x - rhs.x,
y: self.y - rhs.y,
z: self.z - rhs.z,
}
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum BoundaryCondition<T> {
Potential(T),
Flux(T),
}
#[allow(dead_code)]
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct Element2D {
pub p1: Vector2D,
pub p2: Vector2D,
pub midpoint: Vector2D,
pub length: f64,
pub normal: Vector2D,
}
impl Element2D {
#[must_use]
pub fn new(
p1: Vector2D,
p2: Vector2D,
) -> Self {
let diff = p2 - p1;
let length = diff.norm();
let normal = Vector2D::new(diff.y / length, -diff.x / length);
let midpoint = Vector2D::new(f64::midpoint(p1.x, p2.x), f64::midpoint(p1.y, p2.y));
Self {
p1,
p2,
midpoint,
length,
normal,
}
}
}
pub fn solve_laplace_bem_2d(
points: &[(f64, f64)],
bcs: &[BoundaryCondition<f64>],
) -> Result<(Vec<f64>, Vec<f64>), String> {
let n = points.len();
if n != bcs.len() {
return Err("Number of points and \
boundary conditions must \
match."
.to_string());
}
let elements: Vec<_> = (0..n)
.map(|i| {
Element2D::new(
Vector2D::new(points[i].0, points[i].1),
Vector2D::new(points[(i + 1) % n].0, points[(i + 1) % n].1),
)
})
.collect();
let mut h_mat = Matrix::zeros(n, n);
let mut g_mat = Matrix::zeros(n, n);
let matrices_data: Vec<Vec<(usize, usize, f64, f64)>> = (0..n)
.into_par_iter()
.map(|i| {
let mut row = Vec::with_capacity(n);
for j in 0..n {
if i == j {
let g_ii = elements[i].length / (2.0 * std::f64::consts::PI)
* (1.0 - (elements[i].length / 2.0).ln());
row.push((i, j, 0.0, g_ii)); } else {
let r_vec = elements[j].midpoint - elements[i].midpoint;
let r = r_vec.norm();
let dot = r_vec
.x
.mul_add(elements[j].normal.x, r_vec.y * elements[j].normal.y);
let h_ij = -dot / (2.0 * std::f64::consts::PI * r * r);
let g_ij = -1.0 / (2.0 * std::f64::consts::PI) * r.ln();
row.push((i, j, h_ij * elements[j].length, g_ij * elements[j].length));
}
}
row
})
.collect();
for row in matrices_data {
for (i, j, h, g) in row {
*h_mat.get_mut(i, j) = h;
*g_mat.get_mut(i, j) = g;
}
}
for i in 0..n {
let mut row_sum = 0.0;
for j in 0..n {
if i != j {
row_sum += *h_mat.get(i, j);
}
}
*h_mat.get_mut(i, i) = -row_sum;
}
let mut a_mat = Matrix::zeros(n, n);
let mut b_vec = vec![0.0; n];
for (i, b_val) in b_vec.iter_mut().enumerate() {
for (j, bc) in bcs.iter().enumerate() {
match bc {
| BoundaryCondition::Potential(u_val) => {
*a_mat.get_mut(i, j) = -*g_mat.get(i, j);
*b_val -= *h_mat.get(i, j) * u_val;
},
| BoundaryCondition::Flux(q_val) => {
*a_mat.get_mut(i, j) = *h_mat.get(i, j);
*b_val += *g_mat.get(i, j) * q_val;
},
}
}
}
let solution = match solve_linear_system(&a_mat, &b_vec)? {
| LinearSolution::Unique(sol) => sol,
| _ => return Err("BEM system has no unique solution.".to_string()),
};
let mut u = vec![0.0; n];
let mut q = vec![0.0; n];
let mut sol_idx = 0;
for i in 0..n {
match bcs[i] {
| BoundaryCondition::Potential(u_val) => {
u[i] = u_val;
q[i] = solution[sol_idx];
sol_idx += 1;
},
| BoundaryCondition::Flux(q_val) => {
q[i] = q_val;
u[i] = solution[sol_idx];
sol_idx += 1;
},
}
}
Ok((u, q))
}
pub fn simulate_2d_cylinder_scenario() -> Result<(Vec<f64>, Vec<f64>), String> {
let n_points = 40;
let radius = 1.0;
let mut points = Vec::new();
let mut bcs = Vec::new();
for i in 0..n_points {
let angle = 2.0 * std::f64::consts::PI * (f64::from(i)) / (f64::from(n_points));
let (x, y) = (radius * angle.cos(), radius * angle.sin());
points.push((x, y));
bcs.push(BoundaryCondition::Potential(1.0 * x));
}
solve_laplace_bem_2d(&points, &bcs)
}
#[must_use]
pub fn evaluate_potential_2d(
point: (f64, f64),
elements: &[Element2D],
u: &[f64],
q: &[f64],
) -> f64 {
let p = Vector2D::new(point.0, point.1);
let mut result = 0.0;
for i in 0..elements.len() {
let r_vec = elements[i].midpoint - p;
let r = r_vec.norm();
let dot = r_vec
.x
.mul_add(elements[i].normal.x, r_vec.y * elements[i].normal.y);
let h_ij = -dot / (2.0 * std::f64::consts::PI * r * r);
let g_ij = -1.0 / (2.0 * std::f64::consts::PI) * r.ln();
result += (g_ij * elements[i].length).mul_add(q[i], -(h_ij * elements[i].length * u[i]));
}
result
}
pub fn solve_laplace_bem_3d() -> Result<(), String> {
println!(
"3D BEM is a complex topic \
requiring a dedicated \
library. This is a \
placeholder."
);
Ok(())
}