using System; using FluidSim.Components; namespace FluidSim.Core { /// /// Characteristic open‑end boundary condition after Jones (1978). /// For all subsonic flow (outflow and inflow), the ghost state is derived /// from the isentropic expansion to ambient pressure, using the pipe's entropy, /// and the outgoing Riemann invariant. This avoids a density jump at flow reversal. /// Supersonic outflow extrapolates the interior state. /// public class OpenEndLink { public Pipe1D Pipe { get; } public bool IsLeftEnd { get; } public double AmbientPressure { get; set; } = 101325.0; public double Gamma { get; set; } = 1.4; public double LastMassFlowRate { get; private set; } public double LastFaceDensity { get; private set; } public double LastFaceVelocity { get; private set; } public double LastFacePressure { get; private set; } public OpenEndLink(Pipe1D pipe, bool isLeftEnd) { Pipe = pipe ?? throw new ArgumentNullException(nameof(pipe)); IsLeftEnd = isLeftEnd; } public void Resolve(double dtSub) { (double rhoInt, double uInt, double pInt) = IsLeftEnd ? Pipe.GetInteriorStateLeft() : Pipe.GetInteriorStateRight(); double gamma = Gamma; double gm1 = gamma - 1.0; double cInt = Math.Sqrt(gamma * pInt / Math.Max(rhoInt, 1e-12)); double pAmb = AmbientPressure; // Riemann invariants double J_plus = uInt + 2.0 * cInt / gm1; double J_minus = uInt - 2.0 * cInt / gm1; double rhoGhost, uGhost, pGhost; // ---- Subsonic branch (used for both outflow and inflow) ---- // Isentropic expansion to ambient pressure using pipe's entropy double s = pInt / Math.Pow(rhoInt, gamma); // entropy constant double rhoIso = Math.Pow(pAmb / s, 1.0 / gamma); double cIso = Math.Sqrt(gamma * pAmb / Math.Max(rhoIso, 1e-12)); double uIso = IsLeftEnd ? (J_minus + 2.0 * cIso / gm1) : (J_plus - 2.0 * cIso / gm1); // Check for supersonic outflow: if the isentropic velocity exceeds the speed of sound, // the flow is supersonic and we extrapolate the interior state. bool supersonic = IsLeftEnd ? (uInt <= -cInt) // left end: outflow is when u < -c : (uInt >= cInt); // right end: outflow is when u > c // Extra check: if the isentropic velocity is supersonic in the outflow direction, // also treat as supersonic (this can happen when the interior pressure is very high). if (!supersonic) { if (IsLeftEnd) supersonic = uIso <= -cIso; else supersonic = uIso >= cIso; } if (supersonic) { // Supersonic outflow – extrapolate interior rhoGhost = rhoInt; uGhost = uInt; pGhost = pInt; } else { // Subsonic flow – use the isentropic state rhoGhost = rhoIso; uGhost = uIso; pGhost = pAmb; } // Apply ghost to pipe if (IsLeftEnd) Pipe.SetGhostLeft(rhoGhost, uGhost, pGhost); else Pipe.SetGhostRight(rhoGhost, uGhost, pGhost); // Mass flow out of the pipe (positive = leaving) double area = Pipe.Area; double mdot = rhoGhost * uGhost * area; if (IsLeftEnd) mdot = -mdot; // left end: positive u is into pipe, so out is -u LastMassFlowRate = mdot; LastFaceDensity = rhoGhost; LastFaceVelocity = uGhost; LastFacePressure = pGhost; } } }