/** * NavierStokesSolver — Incompressible Navier-Stokes on RegularGrid3D. * * ## Governing Equations * * du/dt + (u·∇)u = -(1/ρ)∇p + ν∇²u + f * ∇·u = 0 (incompressibility) * * ## Algorithm: Chorin's Projection (Fractional Step) * * Each timestep: * 1. **Advection**: Semi-Lagrangian backtrace u*(x) = u(x - u·dt) * Uses RegularGrid3D.sampleAtPositions() for trilinear interpolation. * Unconditionally stable (no CFL restriction on advection). * * 2. **Diffusion**: u** = u* + dt·ν·∇²u* * Uses RegularGrid3D.laplacian() (explicit if CFL ok, otherwise Jacobi). * * 3. **Pressure projection**: Solve ∇²p = (ρ/dt)·∇·u**, then u = u** - (dt/ρ)·∇p * Uses jacobiIteration() for the Poisson equation. * Uses RegularGrid3D.gradient() for pressure gradient correction. * * ## Boundary Conditions * * - No-slip: u = 0 at walls * - Lid-driven: u = U at one face (classic benchmark) * - Inflow/outflow: specified velocity or zero-gradient * * @see RegularGrid3D — field storage with laplacian/gradient/divergence stencils * @see ThermalSolver — same explicit/implicit diffusion pattern */ export type CFDBCType = 'no_slip' | 'lid' | 'inflow' | 'outflow'; export interface CFDBC { face: 'x-' | 'x+' | 'y-' | 'y+' | 'z-' | 'z+'; type: CFDBCType; /** Velocity for lid/inflow [vx, vy, vz] */ velocity?: [number, number, number]; } export interface BodyForce { /** Force vector [fx, fy, fz] in m/s² (acceleration, not force) */ acceleration: [number, number, number]; } export interface NavierStokesConfig { gridResolution: [number, number, number]; domainSize: [number, number, number]; /** Kinematic viscosity ν [m²/s] (default: 1e-6 for water) */ viscosity?: number; /** Fluid density ρ [kg/m³] (default: 1000 for water) */ density?: number; /** Boundary conditions */ boundaryConditions?: CFDBC[]; /** Body forces (gravity, buoyancy, etc.) */ bodyForces?: BodyForce[]; /** Max Jacobi iterations for pressure solve (default: 100) */ pressureIterations?: number; /** Pressure convergence tolerance (default: 1e-4) */ pressureTolerance?: number; } export interface NavierStokesStats { currentTime: number; stepCount: number; maxVelocity: number; maxDivergence: number; pressureIterations: number; } export declare class NavierStokesSolver { private config; private nu; private rho; private vx; private vy; private vz; private vxTemp; private vyTemp; private vzTemp; private pressure; private divergence; private bcMap; private currentTime; private stepCount; private lastPressureIter; constructor(config: NavierStokesConfig); /** * Advance one timestep. * * Fix NSS-2: applyBoundaryConditions is called BEFORE project() so the * divergence stencil at boundary-adjacent cells reads freshly-enforced * values rather than one-step-stale values. */ step(dt: number): void; /** * Semi-Lagrangian advection: trace backwards along velocity field. * u*(x) = u(x - u(x)·dt) */ private advect; /** Apply body forces (gravity, buoyancy). */ private applyBodyForces; /** * Viscous diffusion: u += dt·ν·∇²u (explicit with CFL sub-cycling). * * ## Stability condition (NSS-1 fix) * * dt_visc ≤ 1 / (2·ν·(1/dx² + 1/dy² + 1/dz²)) * * When the caller-supplied dt exceeds this limit, the diffusion step is * sub-cycled with nSub = ceil(dt / dt_visc_stable) substeps of size * subDt = dt / nSub. This mirrors the pattern used in ReactionDiffusionSolver * and ThermalSolver (the house pattern). A safety factor of 0.9 is applied. * * For typical low-viscosity fluids (water ν≈1e-6) at simulation dt values * the guard fires rarely; for high-viscosity fluids (oils, polymers) or fine * grids it prevents the silent NaN propagation that occurred before this fix. */ private diffuse; /** * Pressure projection: enforce ∇·u = 0. * * Rescaled Chorin formulation (avoids the ρ/dt ill-conditioning that * required a ~20 000× tolerance window in the Poiseuille verifier): * * 1. Compute divergence ∇·u* * 2. Solve ∇²φ = ∇·u* (φ = p·dt/ρ, no density/time-scale in RHS) * 3. Correct velocity: u -= ∇φ * * The Jacobi iteration α = dx², β = 6 is now dimensionally consistent * with the RHS, which is O(divergence) rather than O(ρ/dt · divergence). */ private project; /** Apply boundary conditions on all faces. */ private applyBoundaryConditions; getVelocityField(): { vx: Float32Array; vy: Float32Array; vz: Float32Array; }; getVelocityMagnitude(): Float32Array; getPressureField(): Float32Array; getVelocityAt(i: number, j: number, k: number): [number, number, number]; getStats(): NavierStokesStats; dispose(): void; } //# sourceMappingURL=NavierStokesSolver.d.ts.map