Initial commit

This commit is contained in:
redclawsystems
2026-03-04 00:08:42 +00:00
commit 4d88dc0584
4449 changed files with 1556714 additions and 0 deletions
@@ -0,0 +1,371 @@
//! 2D Poisson equation problem
//!
//! Solves: u_xx + u_yy = f(x, y)
//! Domain: (x, y) in [0, 1] x [0, 1]
//! BC: u = 0 on boundary
//! Manufactured solution: u(x, y) = sin(pi*x) * sin(pi*y)
use pinn_benchmark_shared::config::ProblemType;
use rand::Rng;
use crate::analytical::Poisson2DAnalytical;
use super::Problem;
/// 2D Poisson equation problem
pub struct PoissonProblem {
analytical: Poisson2DAnalytical,
x_range: (f64, f64),
y_range: (f64, f64),
}
impl PoissonProblem {
/// Creates a new Poisson problem
#[must_use]
pub fn new() -> Self {
Self {
analytical: Poisson2DAnalytical::new(),
x_range: (0.0, 1.0),
y_range: (0.0, 1.0),
}
}
}
impl Default for PoissonProblem {
fn default() -> Self {
Self::new()
}
}
impl Problem for PoissonProblem {
fn problem_type(&self) -> ProblemType {
ProblemType::Poisson2D
}
fn input_dim(&self) -> usize {
// Poisson is time-independent, but we use 3D input (x, y, dummy_t) for consistency
3
}
fn physics_residual(
&self,
points: &[Vec<f64>],
_u: &[f64],
_du_dx: &[Vec<f64>],
_du_dt: &[f64],
d2u_dx2: &[Vec<f64>],
) -> Vec<f64> {
// Poisson equation: u_xx + u_yy = f(x, y)
let n = points.len();
let mut residual = Vec::with_capacity(n);
for i in 0..n {
let x = points[i][0];
let y = points[i][1];
let u_xx = d2u_dx2[i][0]; // Second derivative w.r.t. x
let u_yy = d2u_dx2[i][1]; // Second derivative w.r.t. y
let f = self.analytical.source_term(x, y);
// Poisson PDE residual: u_xx + u_yy - f = 0
residual.push(u_xx + u_yy - f);
}
residual
}
fn boundary_residual(&self, boundary_points: &[Vec<f64>], u_pred: &[f64]) -> Vec<f64> {
// Dirichlet BC: u = 0 on all boundaries
let mut residual = Vec::with_capacity(u_pred.len());
for (point, &u) in boundary_points.iter().zip(u_pred.iter()) {
let x = point[0];
let y = point[1];
// Check if point is on boundary
let on_boundary = (x - self.x_range.0).abs() < 1e-6
|| (x - self.x_range.1).abs() < 1e-6
|| (y - self.y_range.0).abs() < 1e-6
|| (y - self.y_range.1).abs() < 1e-6;
if on_boundary {
residual.push(u); // Should be 0
} else {
residual.push(0.0); // Interior point, no BC violation
}
}
residual
}
fn initial_residual(&self, _initial_points: &[Vec<f64>], _u_pred: &[f64]) -> Vec<f64> {
// Poisson equation has no initial condition (time-independent)
// Return zero residual
vec![0.0; _u_pred.len()]
}
fn analytical_solution(&self, points: &[Vec<f64>]) -> Option<Vec<f64>> {
let solution: Vec<f64> = points
.iter()
.map(|point| {
let x = point[0];
let y = point[1];
self.analytical.solution(x, y)
})
.collect();
Some(solution)
}
fn generate_collocation_points(&self, n_points: usize) -> Vec<Vec<f64>> {
let mut rng = rand::thread_rng();
let mut points = Vec::with_capacity(n_points);
for _ in 0..n_points {
let x = rng.gen_range(self.x_range.0..self.x_range.1);
let y = rng.gen_range(self.y_range.0..self.y_range.1);
let t = 0.0; // Dummy time coordinate
points.push(vec![x, y, t]);
}
points
}
fn generate_boundary_points(&self, n_points: usize) -> Vec<Vec<f64>> {
let mut rng = rand::thread_rng();
let mut points = Vec::with_capacity(n_points);
// Generate points on all four boundaries
let points_per_edge = n_points / 4;
// Bottom boundary (y = 0)
for _ in 0..points_per_edge {
let x = rng.gen_range(self.x_range.0..self.x_range.1);
points.push(vec![x, self.y_range.0, 0.0]);
}
// Top boundary (y = 1)
for _ in 0..points_per_edge {
let x = rng.gen_range(self.x_range.0..self.x_range.1);
points.push(vec![x, self.y_range.1, 0.0]);
}
// Left boundary (x = 0)
for _ in 0..points_per_edge {
let y = rng.gen_range(self.y_range.0..self.y_range.1);
points.push(vec![self.x_range.0, y, 0.0]);
}
// Right boundary (x = 1)
for _ in 0..points_per_edge {
let y = rng.gen_range(self.y_range.0..self.y_range.1);
points.push(vec![self.x_range.1, y, 0.0]);
}
points
}
fn generate_initial_points(&self, n_points: usize) -> Vec<Vec<f64>> {
// Poisson equation has no initial condition
// Return uniformly distributed interior points
let n_side = (n_points as f64).sqrt() as usize;
let mut points = Vec::with_capacity(n_side * n_side);
for i in 0..n_side {
for j in 0..n_side {
let x = self.x_range.0
+ (i as f64 / (n_side - 1) as f64) * (self.x_range.1 - self.x_range.0);
let y = self.y_range.0
+ (j as f64 / (n_side - 1) as f64) * (self.y_range.1 - self.y_range.0);
points.push(vec![x, y, 0.0]);
}
}
points
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
fn test_poisson_problem_creation() {
let problem = PoissonProblem::new();
assert_eq!(problem.input_dim(), 3); // (x, y, dummy_t)
assert_eq!(problem.output_dim(), 1);
}
#[test]
fn test_physics_residual_zero_for_exact_solution() {
let problem = PoissonProblem::new();
// Test point
let x = 0.5;
let y = 0.5;
let points = vec![vec![x, y, 0.0]];
// Compute exact derivatives analytically
// u(x, y) = sin(pi*x) * sin(pi*y)
// u_xx = -pi^2 * sin(pi*x) * sin(pi*y)
// u_yy = -pi^2 * sin(pi*x) * sin(pi*y)
// f = 2*pi^2 * sin(pi*x) * sin(pi*y)
use std::f64::consts::PI;
let u_xx = -PI.powi(2) * (PI * x).sin() * (PI * y).sin();
let u_yy = -PI.powi(2) * (PI * x).sin() * (PI * y).sin();
let d2u_dx2 = vec![vec![u_xx, u_yy]];
let residual = problem.physics_residual(&points, &[0.0], &vec![], &[], &d2u_dx2);
assert_eq!(residual.len(), 1);
assert_abs_diff_eq!(residual[0], 0.0, epsilon = 1e-10);
}
#[test]
fn test_boundary_residual() {
let problem = PoissonProblem::new();
// Test boundary points
let boundary_points = vec![
vec![0.0, 0.5, 0.0],
vec![1.0, 0.5, 0.0],
vec![0.5, 0.0, 0.0],
vec![0.5, 1.0, 0.0],
];
let u_pred = vec![0.1, 0.2, 0.3, 0.4]; // Non-zero predictions (violate BC)
let residual = problem.boundary_residual(&boundary_points, &u_pred);
assert_eq!(residual.len(), 4);
// At boundaries, residual should equal the prediction (since BC is u=0)
assert_abs_diff_eq!(residual[0], 0.1);
assert_abs_diff_eq!(residual[1], 0.2);
assert_abs_diff_eq!(residual[2], 0.3);
assert_abs_diff_eq!(residual[3], 0.4);
}
#[test]
fn test_boundary_residual_satisfied() {
let problem = PoissonProblem::new();
// Test boundary points with correct BC
let boundary_points = vec![
vec![0.0, 0.5, 0.0],
vec![1.0, 0.5, 0.0],
vec![0.5, 0.0, 0.0],
vec![0.5, 1.0, 0.0],
];
let u_pred = vec![0.0, 0.0, 0.0, 0.0]; // Correct BC
let residual = problem.boundary_residual(&boundary_points, &u_pred);
assert_eq!(residual.len(), 4);
for &r in &residual {
assert_abs_diff_eq!(r, 0.0, epsilon = 1e-10);
}
}
#[test]
fn test_initial_residual_always_zero() {
let problem = PoissonProblem::new();
// Poisson has no initial condition
let points = vec![vec![0.5, 0.5, 0.0]];
let u_pred = vec![1.0];
let residual = problem.initial_residual(&points, &u_pred);
assert_eq!(residual.len(), 1);
assert_abs_diff_eq!(residual[0], 0.0, epsilon = 1e-10);
}
#[test]
fn test_analytical_solution() {
let problem = PoissonProblem::new();
let points = vec![
vec![0.5, 0.5, 0.0],
vec![0.25, 0.25, 0.0],
vec![0.75, 0.75, 0.0],
];
let solution = problem.analytical_solution(&points);
assert!(solution.is_some());
let sol = solution.unwrap();
assert_eq!(sol.len(), 3);
// Maximum should be at (0.5, 0.5)
assert!(sol[0] >= sol[1]);
assert!(sol[0] >= sol[2]);
}
#[test]
fn test_generate_collocation_points() {
let problem = PoissonProblem::new();
let points = problem.generate_collocation_points(100);
assert_eq!(points.len(), 100);
for point in &points {
assert_eq!(point.len(), 3);
assert!(point[0] >= 0.0 && point[0] <= 1.0);
assert!(point[1] >= 0.0 && point[1] <= 1.0);
assert_abs_diff_eq!(point[2], 0.0, epsilon = 1e-10); // Dummy t coordinate
}
}
#[test]
fn test_generate_boundary_points() {
let problem = PoissonProblem::new();
let points = problem.generate_boundary_points(40);
// Should have approximately 10 points per edge
assert!(points.len() >= 36); // At least 9 per edge
for point in &points {
assert_eq!(point.len(), 3);
// At least one coordinate should be at a boundary
let at_boundary = (point[0] - 0.0).abs() < 1e-6
|| (point[0] - 1.0).abs() < 1e-6
|| (point[1] - 0.0).abs() < 1e-6
|| (point[1] - 1.0).abs() < 1e-6;
assert!(at_boundary, "Point ({}, {}) should be on boundary", point[0], point[1]);
}
}
#[test]
fn test_generate_initial_points() {
let problem = PoissonProblem::new();
let points = problem.generate_initial_points(16);
// Should generate a grid of points
assert!(!points.is_empty());
for point in &points {
assert_eq!(point.len(), 3);
assert!(point[0] >= 0.0 && point[0] <= 1.0);
assert!(point[1] >= 0.0 && point[1] <= 1.0);
}
}
#[test]
fn test_source_term() {
let problem = PoissonProblem::new();
// Test that source term is computed correctly
let x = 0.5;
let y = 0.5;
let f = problem.analytical.source_term(x, y);
use std::f64::consts::PI;
let expected = -2.0 * PI.powi(2) * (PI * x).sin() * (PI * y).sin();
assert_abs_diff_eq!(f, expected, epsilon = 1e-10);
}
}