Initial commit
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user