use crate::error::{IntegrateError, IntegrateResult};
use scirs2_core::ndarray::Array2;
pub fn polygon_centroid_and_diameter(vertices: &[[f64; 2]]) -> ([f64; 2], f64) {
let n = vertices.len();
debug_assert!(n >= 3, "Polygon must have at least 3 vertices");
let mut area = 0.0_f64;
let mut cx = 0.0_f64;
let mut cy = 0.0_f64;
for i in 0..n {
let j = (i + 1) % n;
let cross = vertices[i][0] * vertices[j][1] - vertices[j][0] * vertices[i][1];
area += cross;
cx += (vertices[i][0] + vertices[j][0]) * cross;
cy += (vertices[i][1] + vertices[j][1]) * cross;
}
area *= 0.5;
if area.abs() < 1e-14 {
let cx_avg = vertices.iter().map(|v| v[0]).sum::<f64>() / n as f64;
let cy_avg = vertices.iter().map(|v| v[1]).sum::<f64>() / n as f64;
let diam = max_vertex_distance(vertices);
return ([cx_avg, cy_avg], diam.max(1e-14));
}
cx /= 6.0 * area;
cy /= 6.0 * area;
let diam = max_vertex_distance(vertices);
([cx, cy], diam.max(1e-14))
}
pub fn max_vertex_distance(vertices: &[[f64; 2]]) -> f64 {
let n = vertices.len();
let mut diam = 0.0_f64;
for i in 0..n {
for j in (i + 1)..n {
let dx = vertices[j][0] - vertices[i][0];
let dy = vertices[j][1] - vertices[i][1];
let d = (dx * dx + dy * dy).sqrt();
if d > diam {
diam = d;
}
}
}
diam
}
pub fn scaled_monomials_values(x: f64, y: f64, centroid: [f64; 2], diameter: f64) -> [f64; 3] {
[
1.0,
(x - centroid[0]) / diameter,
(y - centroid[1]) / diameter,
]
}
pub fn scaled_monomial_gradients(diameter: f64) -> [[f64; 2]; 3] {
[
[0.0, 0.0], [1.0 / diameter, 0.0], [0.0, 1.0 / diameter], ]
}
fn gauss_2pt_01() -> ([f64; 2], [f64; 2]) {
let pts = [0.5 - 0.5 / 3.0_f64.sqrt(), 0.5 + 0.5 / 3.0_f64.sqrt()];
let weights = [0.5, 0.5];
(pts, weights)
}
fn gram_matrix_gradients(vertices: &[[f64; 2]], centroid: [f64; 2], diameter: f64) -> Array2<f64> {
let area = polygon_area(vertices);
let grads = scaled_monomial_gradients(diameter);
let n_mono = 3;
let mut g = Array2::<f64>::zeros((n_mono, n_mono));
for i in 0..n_mono {
for j in 0..n_mono {
g[[i, j]] = area * (grads[i][0] * grads[j][0] + grads[i][1] * grads[j][1]);
}
}
let _ = centroid; g
}
pub fn polygon_area(vertices: &[[f64; 2]]) -> f64 {
let n = vertices.len();
let mut area = 0.0_f64;
for i in 0..n {
let j = (i + 1) % n;
area += vertices[i][0] * vertices[j][1];
area -= vertices[j][0] * vertices[i][1];
}
(area * 0.5).abs()
}
pub fn compute_pi_nabla(
vertices: &[[f64; 2]],
centroid: [f64; 2],
diameter: f64,
) -> IntegrateResult<Array2<f64>> {
let n_v = vertices.len();
let n_mono = 3;
if n_v < 3 {
return Err(IntegrateError::InvalidInput(
"Polygon must have at least 3 vertices".to_string(),
));
}
let grads = scaled_monomial_gradients(diameter);
let mut b_mat = Array2::<f64>::zeros((n_mono, n_v));
for edge_start in 0..n_v {
let edge_end = (edge_start + 1) % n_v;
let va = vertices[edge_start];
let vb = vertices[edge_end];
let n_unnorm = [vb[1] - va[1], -(vb[0] - va[0])];
for alpha in 1..n_mono {
let grad_dot_n = grads[alpha][0] * n_unnorm[0] + grads[alpha][1] * n_unnorm[1];
b_mat[[alpha, edge_start]] += grad_dot_n * 0.5;
b_mat[[alpha, edge_end]] += grad_dot_n * 0.5;
}
}
for i in 0..n_v {
b_mat[[0, i]] = 1.0 / (n_v as f64);
}
let g_mat = gram_matrix_gradients(vertices, centroid, diameter);
let mut pi_nabla = Array2::<f64>::zeros((n_mono, n_v));
for i in 0..n_v {
pi_nabla[[0, i]] = b_mat[[0, i]];
}
let g11 = g_mat[[1, 1]];
let g12 = g_mat[[1, 2]];
let g21 = g_mat[[2, 1]];
let g22 = g_mat[[2, 2]];
let g_det = g11 * g22 - g12 * g21;
if g_det.abs() < 1e-20 {
return Err(IntegrateError::LinearSolveError(format!(
"Degenerate Gram matrix in Pi_nabla computation, det={g_det}"
)));
}
let g_inv_11 = g22 / g_det;
let g_inv_12 = -g12 / g_det;
let g_inv_21 = -g21 / g_det;
let g_inv_22 = g11 / g_det;
for i in 0..n_v {
let b1 = b_mat[[1, i]];
let b2 = b_mat[[2, i]];
pi_nabla[[1, i]] = g_inv_11 * b1 + g_inv_12 * b2;
pi_nabla[[2, i]] = g_inv_21 * b1 + g_inv_22 * b2;
}
Ok(pi_nabla)
}
pub fn compute_pi0(
vertices: &[[f64; 2]],
centroid: [f64; 2],
diameter: f64,
pi_nabla: &Array2<f64>,
) -> Array2<f64> {
let n_v = vertices.len();
let n_mono = 3;
let _ = (centroid, diameter); let mut pi0 = Array2::<f64>::zeros((n_mono, n_v));
for i in 0..n_mono {
for j in 0..n_v {
pi0[[i, j]] = pi_nabla[[i, j]];
}
}
pi0
}
pub fn eval_pi_nabla_at(
x: f64,
y: f64,
centroid: [f64; 2],
diameter: f64,
pi_nabla: &Array2<f64>,
vertex_values: &[f64],
) -> f64 {
let mono_vals = scaled_monomials_values(x, y, centroid, diameter);
let n_mono = pi_nabla.nrows();
let n_v = vertex_values.len();
let mut result = 0.0_f64;
for alpha in 0..n_mono {
let mut c_alpha = 0.0_f64;
for i in 0..n_v {
c_alpha += pi_nabla[[alpha, i]] * vertex_values[i];
}
result += c_alpha * mono_vals[alpha];
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_polygon_centroid_square() {
let verts = [[0.0_f64, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
let (centroid, diameter) = polygon_centroid_and_diameter(&verts);
assert!((centroid[0] - 0.5).abs() < 1e-12);
assert!((centroid[1] - 0.5).abs() < 1e-12);
assert!((diameter - 2.0_f64.sqrt()).abs() < 1e-12);
}
#[test]
fn test_polygon_centroid_triangle() {
let verts = [[0.0_f64, 0.0], [1.0, 0.0], [0.5, 1.0]];
let (centroid, _diameter) = polygon_centroid_and_diameter(&verts);
assert!((centroid[0] - (0.0 + 1.0 + 0.5) / 3.0).abs() < 1e-12);
assert!((centroid[1] - (0.0 + 0.0 + 1.0) / 3.0).abs() < 1e-12);
}
#[test]
fn test_polygon_diameter_square() {
let verts = [[0.0_f64, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
let (_, diameter) = polygon_centroid_and_diameter(&verts);
assert!(
(diameter - 2.0_f64.sqrt()).abs() < 1e-12,
"diameter={diameter}"
);
}
#[test]
fn test_pi_nabla_reproduces_linear_square() {
let verts = [[0.0_f64, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
let (centroid, diameter) = polygon_centroid_and_diameter(&verts);
let pi_nabla = compute_pi_nabla(&verts, centroid, diameter).unwrap();
let vertex_values: Vec<f64> = verts.iter().map(|v| v[0] + v[1]).collect();
let test_x = 0.5_f64;
let test_y = 0.3_f64;
let u_proj = eval_pi_nabla_at(
test_x,
test_y,
centroid,
diameter,
&pi_nabla,
&vertex_values,
);
let u_exact = test_x + test_y;
assert!(
(u_proj - u_exact).abs() < 1e-10,
"Pi^∇ should reproduce u=x+y exactly, got {u_proj} vs {u_exact}"
);
}
#[test]
fn test_pi_nabla_reproduces_constant() {
let verts = [[0.0_f64, 0.0], [1.0, 0.0], [0.5, 1.0]];
let (centroid, diameter) = polygon_centroid_and_diameter(&verts);
let pi_nabla = compute_pi_nabla(&verts, centroid, diameter).unwrap();
let c = 3.7_f64;
let vertex_values = vec![c, c, c];
let u_proj = eval_pi_nabla_at(0.3, 0.2, centroid, diameter, &pi_nabla, &vertex_values);
assert!(
(u_proj - c).abs() < 1e-10,
"Pi^∇ should reproduce const {c}, got {u_proj}"
);
}
#[test]
fn test_polygon_area_unit_square() {
let verts = [[0.0_f64, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
let area = polygon_area(&verts);
assert!((area - 1.0).abs() < 1e-12, "area={area}");
}
#[test]
fn test_polygon_area_triangle() {
let verts = [[0.0_f64, 0.0], [1.0, 0.0], [0.0, 1.0]];
let area = polygon_area(&verts);
assert!((area - 0.5).abs() < 1e-12, "area={area}");
}
}