using System; using FluidSim.Components; namespace FluidSim.Core { /// /// Characteristic open‑end boundary condition. /// For subsonic outflow the outgoing Riemann invariant is conserved, /// and the ghost pressure is set to the prescribed ambient value. /// 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; // Last resolved state (for audio / monitoring) 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; } /// /// Compute the ghost state and mass flow for one sub‑step. /// 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; double rhoGhost, uGhost, pGhost; double mdot; if (IsLeftEnd) { // Left end: outgoing invariant is J- = u - 2c/(γ-1) double J_minus = uInt - 2.0 * cInt / gm1; if (uInt <= -cInt) // supersonic inflow (all info from outside) { // Simple reservoir model – use ambient density and temperature 300 K rhoGhost = pAmb / (287.0 * 300.0); uGhost = uInt; // keep interior velocity (should be supersonic inward) pGhost = pAmb; } else if (uInt < 0) // subsonic inflow { double rhoAmb = pAmb / (287.0 * 300.0); double cAmb = Math.Sqrt(gamma * pAmb / rhoAmb); uGhost = J_minus + 2.0 * cAmb / gm1; rhoGhost = rhoAmb; pGhost = pAmb; } else // subsonic outflow (uInt >= 0) { double s = pInt / Math.Pow(rhoInt, gamma); rhoGhost = Math.Pow(pAmb / s, 1.0 / gamma); double cGhost = Math.Sqrt(gamma * pAmb / rhoGhost); uGhost = J_minus + 2.0 * cGhost / gm1; if (uGhost < 0) uGhost = 0; pGhost = pAmb; } } else // Right end { // Right end: outgoing invariant is J+ = u + 2c/(γ-1) double J_plus = uInt + 2.0 * cInt / gm1; if (uInt >= cInt) // supersonic outflow { rhoGhost = rhoInt; uGhost = uInt; pGhost = pInt; } else if (uInt >= 0) // subsonic outflow { double s = pInt / Math.Pow(rhoInt, gamma); rhoGhost = Math.Pow(pAmb / s, 1.0 / gamma); double cGhost = Math.Sqrt(gamma * pAmb / rhoGhost); uGhost = J_plus - 2.0 * cGhost / gm1; if (uGhost < 0) uGhost = 0; pGhost = pAmb; } else // subsonic inflow (uInt < 0) { double rhoAmb = pAmb / (287.0 * 300.0); double cAmb = Math.Sqrt(gamma * pAmb / rhoAmb); uGhost = J_plus - 2.0 * cAmb / gm1; rhoGhost = rhoAmb; pGhost = pAmb; } } // Apply ghost to pipe if (IsLeftEnd) Pipe.SetGhostLeft(rhoGhost, uGhost, pGhost); else Pipe.SetGhostRight(rhoGhost, uGhost, pGhost); // Mass flow (positive = out of pipe) double area = Pipe.Area; mdot = rhoGhost * uGhost * area; if (IsLeftEnd) mdot = -mdot; // positive u into pipe, so out of pipe is negative u LastMassFlowRate = mdot; LastFaceDensity = rhoGhost; LastFaceVelocity = uGhost; LastFacePressure = pGhost; } } }