/** * MultiphaseNSSolver — Two-phase incompressible Navier-Stokes with level-set interface tracking. * * Extends NavierStokesSolver's projection method with: * - Level-set field φ (signed distance to interface, φ<0 = liquid, φ>0 = gas) * - Two-fluid density/viscosity blending via smoothed Heaviside * - Surface tension via Continuum Surface Force (CSF) * - Periodic level-set reinitialization to maintain |∇φ| ≈ 1 * * Algorithm per timestep: * 1. Advect φ: dφ/dt + u·∇φ = 0 (semi-Lagrangian) * 2. Reinitialize φ (every N steps) * 3. Compute blended ρ(φ), μ(φ) * 4. Compute surface tension force: F = σ·κ·δ(φ)·∇φ * 5. Advect velocity (semi-Lagrangian) * 6. Diffuse velocity * 7. Pressure projection (variable-density Poisson) * 8. Apply BCs * * @see NavierStokesSolver — single-phase base pattern */ export interface MultiphaseConfig { gridResolution: [number, number, number]; domainSize: [number, number, number]; /** Liquid density [kg/m³] (default: 1000) */ rhoLiquid?: number; /** Gas density [kg/m³] (default: 1.225) */ rhoGas?: number; /** Liquid kinematic viscosity [m²/s] (default: 1e-6) */ nuLiquid?: number; /** Gas kinematic viscosity [m²/s] (default: 1.5e-5) */ nuGas?: number; /** Surface tension coefficient σ [N/m] (default: 0.072 for water-air) */ surfaceTension?: number; /** Gravity acceleration [gx, gy, gz] (default: [0, -9.81, 0]) */ gravity?: [number, number, number]; /** Reinitialize level-set every N steps (default: 5) */ reinitInterval?: number; /** Pressure Jacobi iterations (default: 100) */ pressureIterations?: number; } export interface MultiphaseStats { currentTime: number; stepCount: number; maxVelocity: number; interfaceArea: number; liquidVolumeFraction: number; } export declare class MultiphaseNSSolver { private rhoL; private rhoG; private nuL; private nuG; private sigma; private gravity; private reinitInterval; private pressureIter; private vx; private vy; private vz; private vxTemp; private vyTemp; private vzTemp; private pressure; private invRho; private phi; private phiTemp; private currentTime; private stepCount; constructor(config: MultiphaseConfig); /** Initialize the level-set field from an implicit function. φ<0 = liquid. */ initializeLevelSet(fn: (x: number, y: number, z: number) => number): void; step(dt: number): void; /** Semi-Lagrangian advection of a scalar field. */ private advectField; /** Pressure projection with variable density. * * Uses the rescaled formulation with variable-coefficient Poisson: * Solve ∇·((1/ρ) ∇φ) = ∇·u* (density folded into LHS operator) * Correct: u -= (1/ρ) · ∇φ (local density correction) * * For high density ratios (water:air ~1000:1), the constant-coefficient * Jacobi converges poorly. When pressureIterations >= 200, the solver * switches to the full variable-coefficient Jacobi with harmonic-mean * face coefficients; otherwise it falls back to a density-weighted * constant-coefficient solve that is less accurate but more stable. */ private project; /** * Apply zero-gradient (Neumann) extrapolation to φ boundary cells. * * advectField() only writes interior cells (1..n−2) to its destination * buffer. The six boundary planes are never touched, so they retain * whatever stale value the buffer held from its previous use as the * pressure divergence scratch. Copying the interior-advected result * back into phi via phi.copy(phiTemp) would therefore overwrite phi's * boundary planes with garbage, corrupting every stencil that touches a * boundary-adjacent cell. * * Zero-gradient extrapolation mirrors the nearest interior cell value * onto each boundary face (∂φ/∂n = 0 on all walls), which is the * standard ghost-cell convention for a level-set passing through a * no-penetration boundary. */ private extrapolateLevelSetBC; /** * Reinitialize φ to maintain signed-distance property |∇φ| ≈ 1. * * ## MNS-1 fix: snapshot-based pseudo-time stepping * * The Sussman et al. (1994) reinitialization PDE is: * * dφ/dτ = sign(φ₀)(1 − |∇φ|), φ(x, 0) = φ₀(x) * * Each pseudo-time substep must READ from the φ at the START of the * substep and WRITE to a separate buffer, then swap. The original * code read and wrote the same grid in one pass, making later cells * in the loop see already-updated values from earlier cells of the * same substep — an order-dependent, non-converging update analogous * to Gauss-Seidel on a non-symmetric problem. * * The fix: copy phi → phiTemp at the start of each pseudo-step, * read gradients from phiTemp, write updates to phi, then leave phi * as the new iterate (phiTemp is overwritten next iteration). * * @param iterations Number of pseudo-time substeps. 5 is sufficient * to cover a ~2.5-cell narrow band at dtau = 0.5·dx per substep. */ private reinitializeLevelSet; /** Smoothed Heaviside: H(φ) transitions from 0 (gas) to 1 (liquid) over width ε. */ private smoothedHeaviside; /** Smoothed delta function: derivative of Heaviside. Nonzero only near interface. */ private smoothedDelta; private blendedDensity; private blendedViscosity; /** Curvature κ = ∇·(∇φ/|∇φ|). */ private curvature; private applyWallBC; getLevelSet(): Float32Array; getVelocityMagnitude(): Float32Array; getPressureField(): Float32Array; getStats(): MultiphaseStats; dispose(): void; } //# sourceMappingURL=MultiphaseNSSolver.d.ts.map