/** * ThermalSolver — Heat equation solver via finite differences. * * ## Mathematical Formulation * * **Governing equation** (heat equation with volumetric source): * * ∂T/∂t = α∇²T + Q/(ρcₚ) * * where: * T = temperature field [K or °C] * α = thermal diffusivity = k/(ρcₚ) [m²/s] * k = thermal conductivity [W/(m·K)] * ρ = density [kg/m³] * cₚ = specific heat capacity [J/(kg·K)] * Q = volumetric heat source [W/m³] * * ## Discretization * * **Spatial**: 2nd-order central differences on a uniform 3D grid. * ∇²T ≈ (T_{i+1}-2T_i+T_{i-1})/dx² + (T_{j+1}-2T_j+T_{j-1})/dy² + (T_{k+1}-2T_k+T_{k-1})/dz² * * **Temporal** (explicit mode): Forward Euler. * T^{n+1}_ijk = T^n_ijk + dt * [α∇²T^n_ijk + Q_ijk/(ρcₚ)] * * **Temporal** (implicit mode): Jacobi iteration for the Helmholtz equation. * Activated when dt > dt_stable. Solves the implicit system each timestep. * * ## CFL Stability * * The explicit scheme is conditionally stable. The CFL limit for 3D diffusion is: * dt_stable = 1 / (2α(1/dx² + 1/dy² + 1/dz²)) * * A safety factor of 0.9 is applied: dt_eff = 0.9 * dt_stable. * If the user-specified timestep exceeds this, the solver automatically * switches to implicit Jacobi iteration (unconditionally stable). * * ## Boundary Conditions * * Applied via BoundaryConditions.ts each timestep before integration: * - **Dirichlet**: T_boundary = constant * - **Neumann**: ∂T/∂n = flux (ghost-cell method) * - **Convection**: -k∂T/∂n = h(T - T_amb) → Biot number formulation * - **Robin**: αT + β∂T/∂n = value * * ## Convergence Characteristics * * - Spatial: O(dx²) for smooth solutions * - Temporal (explicit): O(dt) * - Temporal (implicit Jacobi): O(dt) per step, iterations to convergence * * ## Known Limitations * * - Uniform grid only (no adaptive mesh refinement) * - Constant material properties per run (no T-dependent k within a run; * use MaterialProperties.ts for pre-run T-dependent lookup) * - No radiation boundary condition * - No phase change modeling * * ## References * * - Incropera et al., "Fundamentals of Heat and Mass Transfer", 7th ed., Ch. 5 * - Patankar, "Numerical Heat Transfer and Fluid Flow", CRC Press, 1980 * * @see BoundaryConditions — BC application * @see ConvergenceControl — Jacobi iteration for implicit mode * @see MaterialDatabase — Material property lookup */ import { RegularGrid3D } from './RegularGrid3D'; import { type BoundaryCondition } from './BoundaryConditions'; import { type ThermalMaterial } from './MaterialDatabase'; import type { WebGPUAdapterIdentity } from '../gpu/WebGPUContext'; export interface ThermalSource { id: string; type: 'point' | 'volume'; /** Grid-space position [i, j, k] or world-space [x, y, z] */ position: [number, number, number]; /** Heat output in Watts */ heat_output: number; /** Spread radius in cells (for volume sources) */ radius?: number; /** Whether this source is active */ active?: boolean; } export interface ThermalConfig { gridResolution: [number, number, number]; domainSize: [number, number, number]; timeStep: number; /** Material name → thermal properties. Falls back to MaterialDatabase. */ materials: Record>; /** Default material for cells without explicit assignment */ defaultMaterial: string; boundaryConditions: BoundaryCondition[]; sources: ThermalSource[]; /** Initial temperature in domain (°C or K) */ initialTemperature?: number; /** Max implicit solver iterations per step */ maxImplicitIterations?: number; /** Implicit solver convergence tolerance */ implicitTolerance?: number; /** Use WebGPU explicit stencil kernel when available. Implicit Jacobi stays CPU. */ useGPU?: boolean; /** Fail the step instead of falling back when a GPU dispatch is unavailable. */ requireGPU?: boolean; } export interface ThermalStats { minTemperature: number; maxTemperature: number; avgTemperature: number; simulationTime: number; stepCount: number; isImplicit: boolean; usedGPU: boolean; lastStepMs: number; } export declare class ThermalSolver { private temperature; private tempPrev; private sourceField; private config; private material; private alpha; private simulationTime; private stepCount; private useImplicit; private useGPU; private requireGPU; private lastStepUsedGPU; private lastStepMs; private gpuStencil; constructor(config: ThermalConfig); /** * Advance the thermal field by dt seconds. */ step(dt: number): void; /** * Advance the solver, using the WebGPU explicit stencil path when enabled. * Existing synchronous callers keep using step(); GPU aliases call this. */ stepAsync(dt: number): Promise; /** * Explicit forward Euler: T(n+1) = T(n) + dt * (α∇²T + Q/(ρcₚ)) */ private stepExplicit; private stepExplicitGPU; private applyThermalBoundaryConditions; /** * Implicit Jacobi: solve (I - dt·α·∇²)T(n+1) = T(n) + dt·Q/(ρcₚ) * * The Laplacian uses per-axis spacings (dx, dy, dz) — the implicit weights * wᵢ = dt·α/dxᵢ² differ per axis on non-cubic grids (the default thermal * grid is non-cubic: domain [10,5,10] at resolution [64,16,64]). */ private stepImplicit; /** * Rebuild the volumetric source field from config sources. */ private rebuildSourceField; private inBounds; /** Get the temperature field as Float32Array for ScalarFieldOverlay */ getTemperatureField(): Float32Array; /** Get the underlying grid for coupling with other solvers */ getTemperatureGrid(): RegularGrid3D; /** Point query: temperature at world position via trilinear interpolation */ getTemperatureAt(x: number, y: number, z: number): number; /** Update a heat source at runtime (e.g., HVAC on/off) */ setSource(id: string, heatOutput: number, active?: boolean): void; /** Update boundary temperature (e.g., exterior weather change) */ setBoundaryValue(faceOrIndex: string | number, value: number): void; getStats(): ThermalStats; /** Identity of the exact adapter used by the live GPU stencil, when initialized. */ getGPUAdapterIdentity(): WebGPUAdapterIdentity | null; dispose(): void; } //# sourceMappingURL=ThermalSolver.d.ts.map