/** * AdjointHeatSolver — Differentiable thermal diffusion via discrete adjoint. * * ══════════════════════════════════════════════════════════════════════════════ * FORWARD SCHEME * ══════════════════════════════════════════════════════════════════════════════ * * T^{n+1} = T^n + dt * ( alpha * Lap(T^n) + S ) (explicit Euler) * * on a uniform 3D grid with node spacing * * dx = Lx / (nx − 1), dy = Ly / (ny − 1), dz = Lz / (nz − 1) * * matching RegularGrid3D's convention exactly. * * Dirichlet BCs are enforced by overwriting all six face layers after every * explicit update. Interior Laplacian stencil follows RegularGrid3D.laplacian: * second-order central differences, boundary terms skipped (the overwrite * enforces the prescribed value before the next step reads it). * * CFL GUARD * ══════════════════════════════════════════════════════════════════════════════ * * dt_stable = safety * dx_min^2 / (2 * alpha * 3) * * where dx_min = min(dx, dy, dz) and safety = 0.9 by default. * If the requested dt exceeds dt_stable, the solver sub-steps internally so * the gradient computation remains unconditionally valid for any dt the caller * passes. * * DISCRETE ADJOINT * ══════════════════════════════════════════════════════════════════════════════ * * Objective: J = Σ_i w_i * T_i^N * * The forward operator at step n is the linear map * * F: T^n ↦ T^{n+1} = T^n + dt * alpha * L * T^n + dt * S * * where L is the discrete Laplacian matrix (with Dirichlet rows zeroed so * boundary nodes are fixed). F is self-adjoint on a uniform grid with * Dirichlet BCs because L is symmetric and the identity plus a symmetric * matrix is symmetric. Therefore F^T = F (as a matrix), and the adjoint * step is identical to the forward step applied to the co-state lambda: * * λ^N = w * λ^n = λ^{n+1} + dt * alpha * L * λ^{n+1} (backward sweep) * * TRANSPOSE DERIVATION (why F = F^T on uniform Dirichlet grid): * F = I + dt*alpha*L. L is the 7-point finite-difference Laplacian matrix * on the interior nodes, which is symmetric negative-definite (standard * result for the uniform 3D FD Laplacian with homogeneous Dirichlet BCs). * On Dirichlet boundary rows, both L and F reduce to the identity (the * overwrite maps T_bc → T_bc regardless of neighbors). The combined * (interior + boundary) matrix is therefore symmetric, and F^T = F. ∎ * * GRADIENTS * dJ/dS_i = dt * Σ_{n=0}^{N-1} λ_i^{n+1} (chain rule over all steps) * dJ/dT0 = λ^0 (sensitivity to IC) * * MEMORY * O(steps * N) where N = nx*ny*nz. The full forward trajectory is stored * to support the exact discrete adjoint sweep. * * TODO (checkpointing): for very large grids or many steps, replace the * full-trajectory store with a revolve-style checkpointing scheme * (Griewank & Walther 2000) to reduce memory to O(sqrt(steps)*N) at * O(log(steps)) extra forward work. */ export interface AdjointHeatConfig { /** Grid node counts [nx, ny, nz]. Must be ≥ 2 in each dimension. */ resolution: [number, number, number]; /** Physical domain size [Lx, Ly, Lz] in metres (or consistent length units). */ domainSize: [number, number, number]; /** * Thermal diffusivity α [m²/s]. No default — the value is material-specific * and must be supplied by the caller (rule G.GOLD.485: no Earth-hardcoded * physical constants). Typical values: copper ≈ 1.17e-4, steel ≈ 1.2e-5, * air ≈ 2.2e-5, water ≈ 1.43e-7. */ alpha: number; /** * Requested time step [s]. Internally sub-stepped if it exceeds the * explicit-Euler diffusion stability limit. */ dt: number; /** * Volumetric heat source field S [K/s], length nx*ny*nz, row-major (k outer, * j middle, i inner — same as RegularGrid3D). All zeros if omitted. */ source?: Float64Array; /** * Initial temperature field T0 [K or °C], length nx*ny*nz. * Uniform zero if omitted. */ initialT?: Float64Array; /** * Fixed temperature applied to all six boundary faces (Dirichlet BC) [K or °C]. * Default 0. */ boundaryValue?: number; /** * CFL safety factor in (0, 1]. Default 0.9. Reduce for tighter stability * margins; raise toward 1 to minimise sub-step count. */ cflSafety?: number; } export interface GradientResult { /** dJ/dS_i: gradient of J with respect to the source field S, same layout as S. */ dJdS: Float64Array; /** dJ/dT0_i: gradient of J with respect to the initial temperature T0. */ dJdT0: Float64Array; /** The objective value J = Σ_i w_i * T_i^N. */ objective: number; } export declare class AdjointHeatSolver { private readonly nx; private readonly ny; private readonly nz; private readonly N; private readonly dx; private readonly dy; private readonly dz; private readonly alpha; private readonly requestedDt; private readonly subDt; private readonly subStepsPerDt; private readonly S; private readonly T0; private readonly boundaryValue; /** * Trajectory store: trajectory[n] = T^n, n = 0 … stepsRequested. * Populated by forward() and consumed by gradient(). * Memory cost: O(stepsRequested * N) Float64. */ private trajectory; private forwardStepsRun; constructor(config: AdjointHeatConfig); /** Number of sub-steps taken per logical dt. >1 when dt exceeds stability limit. */ get subStepCount(): number; /** The stable sub-step size actually used internally [s]. */ get stableSubDt(): number; /** * Run `steps` logical time steps (each potentially sub-stepped for CFL). * Returns T^N, the final temperature field. * Stores the full trajectory internally for subsequent gradient() call. * * Memory: (steps + 1) * N * 8 bytes of Float64. */ forward(steps: number): Float64Array; /** * Compute gradients of J = Σ_i w_i * T_i^N with respect to the source * field S and the initial condition T0, using the discrete adjoint method. * * Must be called after forward(). * * @param weights Weight field w of length N. Interior values only — boundary * weights are silently ignored (Dirichlet nodes are fixed and * carry no sensitivity). */ gradient(weights: Float64Array): GradientResult; private _applyDirichlet; private _applyDirichletZero; } //# sourceMappingURL=AdjointHeatSolver.d.ts.map