/** * HydraulicSolver — Pipe network solver using Hardy-Cross method. * * ## Mathematical Formulation * * **Governing equations** (steady-state incompressible pipe flow): * * Continuity: Σ Q_in = Σ Q_out (at each node) * Energy: Σ h_f = 0 (around each closed loop) * * where: * Q = volumetric flow rate [m³/s] * h_f = friction head loss [m] * * **Darcy-Weisbach head loss**: * h_f = f · (L/D) · (V²/2g) = f · (L/D) · (8Q²)/(π²gD⁴) * * where: * f = Darcy friction factor [-] * L = pipe length [m] * D = pipe diameter [m] * V = flow velocity [m/s] * g = 9.81 m/s² * * **Friction factor** (Swamee-Jain approximation, turbulent Re > 2300): * f = 0.25 / [log₁₀(ε/(3.7D) + 5.74/Re^0.9)]² * * **Laminar flow** (Re < 2300): * f = 64/Re * * ## Hardy-Cross Iteration * * 1. Detect independent loops via spanning tree (BFS). * 2. Initialize flow rates satisfying continuity at each node. * 3. For each iteration: * a. Compute h_f for each pipe in each loop. * b. Compute correction: ΔQ = -Σh_f / Σ(2|h_f|/|Q|) * c. Apply ΔQ to all pipes in the loop. * 4. Converge when max|ΔQ| < tolerance. * 5. Back-calculate node pressures via BFS from known-head nodes. * * **Tree-topology networks** (zero loops): Direct Bernoulli solve. * When the spanning tree has no independent loops, Hardy-Cross cannot * iterate. Flow rates are computed directly from continuity, and * pressures from Bernoulli along the tree branches. * * ## Valve Modeling * * Valves modify the effective pipe diameter: * D_eff = D × opening_fraction * A fully closed valve (opening=0) sets D_eff to near-zero, * producing very high head loss. * * ## Convergence Characteristics * * - Hardy-Cross converges linearly (first-order) for well-conditioned networks. * - Convergence degrades for networks with very different pipe diameters. * - Typical convergence: 10-50 iterations for engineering accuracy. * * ## Known Limitations * * - Steady-state only (no water hammer / transient analysis) * - Incompressible flow only (no gas networks) * - No pump curves (fixed-head nodes only) * - No minor losses (fittings, bends, valves modeled only as diameter changes) * - Single fluid (uniform viscosity) * * ## References * * - Cross, H., "Analysis of Flow in Networks of Conduits or Conductors", * Univ. of Illinois Bulletin No. 286, 1936 * - Chadwick, Morfett & Borthwick, "Hydraulics in Civil and Environmental * Engineering", 5th ed., CRC Press, 2013 * - Swamee, P.K. & Jain, A.K., "Explicit equations for pipe-flow problems", * J. Hydraulic Division, ASCE, 102(5), 657-664, 1976 * * @see ConvergenceControl — convergence result type * @see MaterialDatabase — pipe roughness lookup */ import { type ConvergenceResult } from './ConvergenceControl'; export interface HydraulicPipe { id: string; diameter: number; length: number; roughness: number; material?: string; } export interface HydraulicNode { id: string; type: 'reservoir' | 'junction'; /** Pressure head for reservoirs (m) */ head?: number; /** Demand flow rate for junctions (m³/s) */ demand?: number; /** Elevation (m) */ elevation?: number; } export interface HydraulicValve { id: string; pipe: string; position: number; opening: number; } export interface HydraulicConfig { pipes: HydraulicPipe[]; nodes: HydraulicNode[]; /** [nodeA_id, pipe_id, nodeB_id] */ connections: [string, string, string][]; valves: HydraulicValve[]; maxIterations: number; convergence: number; /** Fluid kinematic viscosity (m²/s), default water at 20°C */ viscosity?: number; /** Fluid density (kg/m³), default 998 */ density?: number; } export interface HydraulicStats { nodeCount: number; pipeCount: number; loopCount: number; maxPressure: number; minPressure: number; totalDemand: number; solveResult: ConvergenceResult | null; } export declare class HydraulicSolver { private config; private pipes; private nodes; private nodeMap; private pipeMap; private loops; private pressures; private flowRates; private solveResult; private viscosity; constructor(config: HydraulicConfig); /** * Solve the pipe network for steady-state pressures and flow rates. */ solve(): ConvergenceResult; /** * Head loss in a pipe using Darcy-Weisbach: hf = f * (L/D) * (V²/2g) * Sign follows flow direction. */ private headLoss; /** * Compute node pressures by walking from known-head nodes. */ private computeNodePressures; private initialFlowGuess; /** * Exact flow calculation for tree-topology networks (no loops). * Propagates demand from leaf nodes up to reservoirs. */ private solveTreeFlows; /** * Find independent loops using spanning tree fundamental cycles. * * Uses BFS to build a spanning tree, then each non-tree chord (back edge) * defines exactly one fundamental cycle. This guarantees independent loops * (no duplicates or overlaps) which Hardy-Cross requires. * * ## HYD-1 fix: direction tracking * * Each returned LoopEntry carries a `sign` field: * +1 — the loop traverses this pipe in its fromNode→toNode direction * −1 — the loop traverses it in the reverse direction * * Fundamental cycle for chord (u→v): * 1. Chord u→v: sign = +1 * 2. Tree path v → LCA (v going UP to LCA, child→parent at each step): * pipe = pathB[i+1].pipe, fromTraversal = pathB[i].node (child) * 3. Tree path LCA → u (LCA going DOWN to u, parent→child at each step): * pipe = pathA[j+1].pipe, fromTraversal = pathA[j+1].node (parent) * * Note on pathX[i].pipe semantics (from traceToRoot): * pathX[0] = {node: start, pipe: -1} * pathX[i].pipe = pipe that connects pathX[i-1].node (child) to * pathX[i].node (parent) in the BFS tree */ private findLoops; /** Trace a node to root of spanning tree, returning (node, pipe) pairs */ private traceToRoot; private updateOutputArrays; getPressureField(): Float32Array; getFlowRates(): Float32Array; setValveOpening(id: string, opening: number): void; setDemand(nodeId: string, demand: number): void; setPumpPressure(nodeId: string, head: number): void; getStats(): HydraulicStats; dispose(): void; } //# sourceMappingURL=HydraulicSolver.d.ts.map