/** * StructuralSolverTET10 — Quadratic tetrahedral FEM with GPU-accelerated CG. * * ## Mathematical Formulation * * Same governing equations as TET4 (static linear elasticity: Ku = f), * but with 10-node quadratic tetrahedra that eliminate shear locking * and achieve O(h²) convergence for displacements. * * ## Element Formulation * * **Element type**: 10-node quadratic tetrahedron (TET10). * **Shape functions**: Quadratic in barycentric coordinates (L1, L2, L3, L4). * Corner nodes (0-3): Ni = Li(2Li - 1) * Mid-edge nodes (4-9): Nij = 4·Li·Lj * * **Node numbering** (standard Zienkiewicz convention): * 0-3: corner vertices * 4: mid-edge 0→1, 5: mid-edge 1→2, 6: mid-edge 0→2 * 7: mid-edge 0→3, 8: mid-edge 1→3, 9: mid-edge 2→3 * * **Integration**: 4-point Gauss quadrature (exact for quadratic integrands). * Points: (a,b,b), (b,a,b), (b,b,a), (b,b,b) * where a = (5+3√5)/20 ≈ 0.5854102, b = (5-√5)/20 ≈ 0.1381966 * Weight per point: 1/4 (normalized to reference tet volume = 1/6) * * ## Assembly * * **Explicit CSR assembly**: Unlike the TET4 matrix-free approach, TET10 * assembles the full global stiffness matrix in Compressed Sparse Row format. * This enables GPU-accelerated SpMV via `SparseLinearSolver`. * * **GPU path**: When a WebGPUContext is provided and available, the solve * routes through `SparseLinearSolver.solveCG()`. Otherwise falls back to * CPU-based preconditioned conjugate gradient. * * ## Stress Recovery * * Stresses are evaluated at each Gauss point (superconvergent locations) * and averaged per element, giving O(h²) stress accuracy vs O(1) for TET4. * * ## Convergence Characteristics * * - O(h²) for displacements (vs O(h) for TET4) * - O(h²) for stresses at superconvergent points (vs O(1) for TET4) * - No shear locking for bending-dominated problems * - Handles near-incompressible materials better than TET4 * * ## References * * - Zienkiewicz & Taylor, "The Finite Element Method", Vol. 1, 7th ed., Ch. 10 * - Bathe, K.J., "Finite Element Procedures", Prentice Hall, 1996, Ch. 5 * - Hughes, T.J.R., "The Finite Element Method", Dover, 2000 * * @see SparseLinearSolver — GPU CG solver consuming CSR matrices * @see StructuralSolver — TET4 predecessor (matrix-free, CPU only) */ import { type ConvergenceResult } from './ConvergenceControl'; import { type StructuralMaterial } from './MaterialDatabase'; import { type CSRMatrix } from '../gpu/SparseLinearSolver'; import { type Force, type Pressure, type Acceleration } from './units/PhysicalQuantity'; export type ConstraintType = 'fixed' | 'pinned' | 'roller'; export interface TET10Constraint { id: string; type: ConstraintType; /** Node indices that are constrained */ nodes: number[]; /** * For 'roller' type: which translational DOF axes to constrain. * 0 = U_x, 1 = U_y, 2 = U_z. * Omit (or pass all three) to behave like 'fixed'. */ dofs?: (0 | 1 | 2)[]; } export type LoadType = 'gravity' | 'point' | 'distributed'; export interface TET10Load { id: string; type: LoadType; /** Force vector [fx, fy, fz] in N */ force?: [Force, Force, Force]; /** Acceleration [ax, ay, az] for gravity loads */ acceleration?: [Acceleration, Acceleration, Acceleration]; /** Node index for point loads */ nodeIndex?: number; /** * Surface references for distributed loads. * Backward compatible forms: * - element index (assumes local face 0) * - encoded face index: elementIndex * 4 + localFace */ surfaceElements?: number[]; /** Explicit face references (preferred) */ surfaceFaces?: Array<{ elementIndex: number; localFace: 0 | 1 | 2 | 3; }>; /** Pressure in Pa for distributed loads */ pressure?: Pressure; } export interface SurfaceMesh { /** Triangle positions: flat [x0,y0,z0, ...] — 3 verts per face */ positions: Float32Array; /** Per-vertex scalar (interpolated from element scalars) */ scalars: Float32Array; /** Per-vertex normals for lighting */ normals: Float32Array; /** Volume node indices for zero-copy lookups */ volumeNodeIndices: Uint32Array; /** Number of triangles */ triangleCount: number; } export interface TET10Config { /** Vertex positions: flat [x0,y0,z0, x1,y1,z1, ...] — all 10 nodes per element */ vertices: Float64Array | Float32Array; /** Element connectivity: flat [n0,...,n9, ...] — 10 nodes per element */ tetrahedra: Uint32Array; /** Material name or direct properties */ material: string | StructuralMaterial; constraints: TET10Constraint[]; loads: TET10Load[]; /** Max CG iterations (default 2000) */ maxIterations?: number; /** CG convergence tolerance (default 1e-10) */ tolerance?: number; /** Use GPU solver when available (default true) */ useGPU?: boolean; /** Enable geometric nonlinearity (Newton-Raphson) */ nonlinear?: boolean; /** Number of load steps for nonlinear analysis (default 5) */ loadSteps?: number; } export interface TET10Stats { nodeCount: number; elementCount: number; dofCount: number; nnz: number; maxVonMises: number; minSafetyFactor: number; solveResult: ConvergenceResult | null; solveTimeMs: number; useGPU: boolean; } export declare class StructuralSolverTET10 { private config; private material; private maxIterations; private tolerance; private useGPU; private nonlinear; private loadSteps; private nodeCount; private elementCount; private dofCount; private referencePositions; private displacements; private externalForces; private vonMisesStress; private cauchyStress; private safetyFactors; /** * Per-Gauss-point stress data for SPR recovery. * Layout: elementCount × 4 Gauss points × 6 stress components = elementCount × 24. * Access: gaussPointStress[(e * 4 + gp) * 6 + component] * Preserved during recoverStress() instead of being discarded after averaging. */ private gaussPointStress; /** * Physical (x,y,z) coordinates of each Gauss point. * Layout: elementCount × 4 × 3 = elementCount × 12. * Access: gaussPointCoords[(e * 4 + gp) * 3 + axis] * Needed by SPR to build the polynomial fitting problem. */ private gaussPointCoords; private constrainedDofs; private csrRowPtr; private csrColInd; private csrVal; private dofToCSR; private nnz; private D; private solveResult; private solveTimeMs; /** Helper for 3x3 matrix inversion */ private invert3x3; private gpuDisplacementBuffer; private gpuSolver; private static readonly LOCAL_FACE_NODE_MAP; private decodeSurfaceReferences; private get stiffnessMatrix(); constructor(config: TET10Config); /** * Assemble the global stiffness matrix in CSR format. * * For each element: * 1. Evaluate shape function gradients at each Gauss point * 2. Compute Jacobian and its inverse * 3. Transform gradients to physical coordinates: dN/dx = J^{-1} · dN/dξ * 4. Build B matrix (6×30) and element stiffness Ke = Σ_gp (w · |J| · Bᵀ·D·B) * 5. Scatter Ke into the global CSR structure */ private assembleGlobalStiffness; /** * Assemble the global force vector from loads. * Uses shape functions for consistent force distribution. */ private assembleForces; /** * Solve the nonlinear structural problem using Newton-Raphson iterations. * * Workflow (as per RESEARCH_NONLINEAR_NR.md): * 1. Loop through load increments * 2. Outer loop: re-assemble tangent stiffness K_T(u) and internal force f_int(u) * 3. Inner solve: K_T * delta_u = f_ext - f_int * 4. Update: u = u + delta_u * 5. Check convergence: ||f_ext - f_int|| < tol */ solveNonlinear(): Promise; /** * Assemble internal force vector f_int based on current displacements (Large Strain). * f_int = \int B^T * sigma dV */ private assembleInternalForce; /** * Assemble tangent stiffness matrix K_T = K_material + K_geometric. * * K_material: standard BᵀDB (same as linear, but B evaluated at deformed config) * K_geometric: accounts for stress stiffening/softening under large deformation * K_G[ab] = δᵢⱼ Σ_gp (w · |J| · Σ_kl dNa/dXk · S_kl · dNb/dXl) */ private assembleTangentStiffness; /** * Add a light diagonal regularization to unconstrained DOFs in tangent matrix * to reduce near-singular NR steps in early nonlinear iterations. */ private regularizeTangentStiffness; /** Alias for backward compatibility with StructuralSolver interface */ private assembleStiffness; /** Wrapper for linear solvers (CPU or GPU) */ private solveLinearSystem; private readbackGPUDisplacements; private multiplyStiffness; private localConjugateGradient; private dot; /** Internal GPU solve path using SparseLinearSolver */ private solveGPU; solve(): Promise; /** * Synchronous CPU-only solve (for environments without WebGPU). */ solveCPU(rhs?: Float64Array): ConvergenceResult; /** // Note: we still read back for scalar fields (VM Stress) if needed, // but we can skip readback for displacements if the renderer uses this buffer. const solution = await solver.readback(gpuResult.xBuffer, this.dofCount); solver.destroy(); // Copy solution to Float64 for (let i = 0; i < this.dofCount; i++) { this.displacements[i] = solution[i]; } return { converged: gpuResult.converged, iterations: gpuResult.iterations, residual: Math.sqrt(gpuResult.residualNormSq), maxChange: 0, }; } catch { // GPU unavailable — fall through to CPU return null; } } /** * Apply Dirichlet constraints to the CSR matrix. * Constrained rows become identity rows (diagonal = 1, off-diagonals = 0). * Corresponding columns are also zeroed for symmetry preservation. */ private applyConstraintsToCSR; /** * Recover Von Mises stress per element, averaged over Gauss points. * Stresses at Gauss points are superconvergent for TET10. * Also stores per-Gauss-point stress and coordinates for SPR recovery. */ private recoverStress; getVonMisesStress(): Float64Array; /** Per-element Cauchy stress tensor averaged over Gauss points: [sxx,syy,szz,txy,tyz,txz] × elementCount */ getCauchyStress(): Float64Array; /** * Per-Gauss-point stress data for SPR recovery. * Layout: (elementCount × 4) × 6 components. * Access: result[(e * 4 + gp) * 6 + component] * Components: 0=sxx, 1=syy, 2=szz, 3=txy, 4=tyz, 5=txz */ getGaussPointStress(): Float64Array; /** * Physical coordinates of each Gauss point. * Layout: (elementCount × 4) × 3. * Access: result[(e * 4 + gp) * 3 + axis] */ getGaussPointCoords(): Float64Array; getSafetyFactor(): Float64Array; getDisplacements(): Float64Array; getExternalForces(): Float64Array; getCSRMatrix(): CSRMatrix; getStats(): TET10Stats; getDisplacementBuffer(): GPUBuffer | null; readbackOutput(): Promise; dispose(): void; } /** * Convert a TET4 mesh to TET10 by inserting mid-edge nodes. * Takes flat vertex and tet arrays, returns new arrays with mid-edge nodes added. */ export declare function tet4ToTet10(vertices: Float64Array | Float32Array, tetrahedra: Uint32Array): { vertices: Float64Array; tetrahedra: Uint32Array; }; //# sourceMappingURL=StructuralSolverTET10.d.ts.map