Spaces:
Sleeping
Sleeping
| """Comprehensive FEM Tests - 100% Vendor-Based Testing | |
| Tests for mesh generation, boundary conditions, and FEM solver. | |
| Uses pytest and numpy for validation. | |
| """ | |
| import pytest | |
| import numpy as np | |
| import sys | |
| from pathlib import Path | |
| # Add parent directory to path | |
| sys.path.insert(0, str(Path(__file__).parent.parent)) | |
| try: | |
| import skfem | |
| SKFEM_AVAILABLE = True | |
| except ImportError: | |
| SKFEM_AVAILABLE = False | |
| try: | |
| import pygmsh | |
| import meshio | |
| MESH_LIBS_AVAILABLE = True | |
| except ImportError: | |
| MESH_LIBS_AVAILABLE = False | |
| pytestmark = pytest.mark.skipif( | |
| not (SKFEM_AVAILABLE and MESH_LIBS_AVAILABLE), | |
| reason="FEM libraries not available" | |
| ) | |
| class TestMeshGenerator: | |
| """Test mesh generation using vendor libraries.""" | |
| def test_import_mesh_generator(self): | |
| """Test mesh_generator module imports.""" | |
| from fem_core.mesh_generator import MeshGenerator | |
| assert MeshGenerator is not None | |
| def test_rectangle_mesh_generation(self): | |
| """Test 2D rectangular mesh generation.""" | |
| from fem_core.mesh_generator import MeshGenerator | |
| gen = MeshGenerator() | |
| mesh = gen.generate_rectangle_mesh(width=2.0, height=1.0, nx=5, ny=5) | |
| assert mesh is not None | |
| assert len(mesh.points) > 0 | |
| assert len(mesh.cells) > 0 | |
| def test_circle_mesh_generation(self): | |
| """Test circular mesh generation.""" | |
| from fem_core.mesh_generator import MeshGenerator | |
| gen = MeshGenerator() | |
| mesh = gen.generate_circle_mesh(radius=0.5, mesh_size=0.1) | |
| assert mesh is not None | |
| assert len(mesh.points) > 0 | |
| def test_mesh_info(self): | |
| """Test mesh information extraction.""" | |
| from fem_core.mesh_generator import MeshGenerator | |
| gen = MeshGenerator() | |
| mesh = gen.generate_rectangle_mesh(1.0, 1.0, 3, 3) | |
| info = gen.get_mesh_info(mesh) | |
| assert 'num_points' in info | |
| assert 'num_cells' in info | |
| assert info['num_points'] > 0 | |
| assert info['num_cells'] > 0 | |
| class TestBoundaryConditions: | |
| """Test boundary condition handling.""" | |
| def test_import_boundary_conditions(self): | |
| """Test boundary_conditions module imports.""" | |
| from fem_core.boundary_conditions import ( | |
| BoundaryCondition, | |
| BoundaryType, | |
| BoundaryConditionHandler | |
| ) | |
| assert BoundaryCondition is not None | |
| assert BoundaryType is not None | |
| def test_dirichlet_bc_creation(self): | |
| """Test Dirichlet BC creation.""" | |
| from fem_core.boundary_conditions import ( | |
| create_dirichlet_bc, | |
| BoundaryType | |
| ) | |
| bc = create_dirichlet_bc(value=1.0) | |
| assert bc.bc_type == BoundaryType.DIRICHLET | |
| assert bc.value == 1.0 | |
| def test_neumann_bc_creation(self): | |
| """Test Neumann BC creation.""" | |
| from fem_core.boundary_conditions import ( | |
| create_neumann_bc, | |
| BoundaryType | |
| ) | |
| bc = create_neumann_bc(value=0.5) | |
| assert bc.bc_type == BoundaryType.NEUMANN | |
| assert bc.value == 0.5 | |
| def test_bc_evaluation(self): | |
| """Test BC evaluation at points.""" | |
| from fem_core.boundary_conditions import create_dirichlet_bc | |
| # Constant value | |
| bc = create_dirichlet_bc(value=2.0) | |
| x = np.array([[0.0, 0.0], [1.0, 1.0]]) | |
| values = bc.evaluate(x) | |
| assert np.allclose(values, 2.0) | |
| # Function value | |
| bc_func = create_dirichlet_bc(value=lambda x: np.sin(x[0])) | |
| values = bc_func.evaluate(x) | |
| assert len(values) == 2 | |
| class TestFEMSolver: | |
| """Test FEM solver functionality.""" | |
| def test_import_solver(self): | |
| """Test solver module imports.""" | |
| from fem_core.solver import FEMSolver, solve_poisson_2d | |
| assert FEMSolver is not None | |
| assert solve_poisson_2d is not None | |
| def test_solver_initialization(self): | |
| """Test FEM solver initialization.""" | |
| from fem_core.solver import FEMSolver | |
| # Create simple mesh using scikit-fem | |
| mesh = skfem.MeshTri() | |
| solver = FEMSolver(mesh) | |
| assert solver.mesh is not None | |
| assert solver.basis is not None | |
| def test_poisson_solve_simple(self): | |
| """Test Poisson equation with simple source.""" | |
| from fem_core.solver import FEMSolver | |
| # Unit square mesh | |
| mesh = skfem.MeshTri() | |
| mesh = mesh.refined(2) # Refine for better accuracy | |
| solver = FEMSolver(mesh) | |
| # Constant source term | |
| def source(x): | |
| return np.ones_like(x[0]) | |
| solution = solver.solve_poisson(source, dirichlet_val=0.0) | |
| assert solution is not None | |
| assert len(solution) == solver.basis.N | |
| assert np.all(np.isfinite(solution)) | |
| def test_poisson_manufactured_solution(self): | |
| """Test Poisson with manufactured solution.""" | |
| from fem_core.solver import FEMSolver | |
| # Manufactured solution: u = x*(1-x)*y*(1-y) | |
| # Then -Laplacian(u) = 2*y*(1-y) + 2*x*(1-x) | |
| mesh = skfem.MeshTri() | |
| mesh = mesh.refined(3) | |
| solver = FEMSolver(mesh) | |
| def source(x): | |
| return 2*x[1]*(1-x[1]) + 2*x[0]*(1-x[0]) | |
| def exact(x): | |
| return x[0]*(1-x[0])*x[1]*(1-x[1]) | |
| solution = solver.solve_poisson(source, dirichlet_val=0.0) | |
| # Check boundary conditions | |
| boundary_dofs = solver.basis.get_dofs() | |
| assert np.allclose(solution[boundary_dofs], 0.0, atol=1e-10) | |
| def test_helmholtz_solve(self): | |
| """Test Helmholtz equation solver.""" | |
| from fem_core.solver import FEMSolver | |
| mesh = skfem.MeshTri() | |
| mesh = mesh.refined(2) | |
| solver = FEMSolver(mesh) | |
| k_squared = 1.0 | |
| def source(x): | |
| return np.ones_like(x[0]) | |
| solution = solver.solve_helmholtz(k_squared, source, dirichlet_val=0.0) | |
| assert solution is not None | |
| assert np.all(np.isfinite(solution)) | |
| class TestIntegration: | |
| """Integration tests for complete workflows.""" | |
| def test_full_poisson_workflow(self): | |
| """Test complete Poisson solve workflow.""" | |
| from fem_core.mesh_generator import create_unit_square_mesh | |
| from fem_core.solver import solve_poisson_2d | |
| # Generate mesh | |
| mesh = create_unit_square_mesh(n=5) | |
| # Define problem | |
| def source(x): | |
| return -2.0 * (x[0]**2 + x[1]**2) | |
| # Solve | |
| solution, solver = solve_poisson_2d(mesh, source, bc_value=0.0) | |
| assert solution is not None | |
| assert solver is not None | |
| assert len(solution) > 0 | |
| def test_convergence_rate(self): | |
| """Test mesh convergence for Poisson equation.""" | |
| from fem_core.solver import FEMSolver | |
| # Manufactured solution | |
| def exact(x): | |
| return np.sin(np.pi*x[0]) * np.sin(np.pi*x[1]) | |
| def source(x): | |
| return 2*np.pi**2 * np.sin(np.pi*x[0]) * np.sin(np.pi*x[1]) | |
| errors = [] | |
| mesh_sizes = [] | |
| for refinement in [1, 2, 3]: | |
| mesh = skfem.MeshTri() | |
| mesh = mesh.refined(refinement) | |
| solver = FEMSolver(mesh) | |
| solution = solver.solve_poisson(source, dirichlet_val=0.0) | |
| # Compute L2 error (simplified) | |
| points = solver.basis.doflocs | |
| exact_vals = exact(points) | |
| error = np.linalg.norm(solution - exact_vals) / np.sqrt(len(solution)) | |
| errors.append(error) | |
| mesh_sizes.append(1.0 / (2**refinement)) | |
| # Check that error decreases with refinement | |
| assert errors[0] > errors[1] > errors[2] | |
| # Empirical validation tests | |
| class TestEmpiricalValidation: | |
| """Empirical validation of FEM implementation.""" | |
| def test_symmetry_of_stiffness_matrix(self): | |
| """Verify stiffness matrix is symmetric.""" | |
| from fem_core.solver import FEMSolver | |
| from skfem import BilinearForm, asm | |
| from skfem.helpers import dot, grad | |
| mesh = skfem.MeshTri() | |
| solver = FEMSolver(mesh) | |
| def laplacian(u, v, _): | |
| return dot(grad(u), grad(v)) | |
| K = asm(laplacian, solver.basis) | |
| # Check symmetry | |
| assert np.allclose(K.toarray(), K.T.toarray()) | |
| def test_mass_conservation(self): | |
| """Test mass conservation in heat equation.""" | |
| # Placeholder for heat equation mass conservation test | |
| assert True | |
| def test_boundary_value_enforcement(self): | |
| """Verify boundary values are enforced correctly.""" | |
| from fem_core.solver import FEMSolver | |
| mesh = skfem.MeshTri() | |
| mesh = mesh.refined(2) | |
| solver = FEMSolver(mesh) | |
| def source(x): | |
| return np.ones_like(x[0]) | |
| bc_value = 5.0 | |
| solution = solver.solve_poisson(source, dirichlet_val=bc_value) | |
| boundary_dofs = solver.basis.get_dofs() | |
| # All boundary DOFs should equal bc_value | |
| assert np.allclose(solution[boundary_dofs], bc_value, atol=1e-10) | |
| if __name__ == "__main__": | |
| pytest.main([__file__, "-v"]) |