using System; using FluidSim.Components; using FluidSim.Interfaces; namespace FluidSim.Core { public class OrificeLink { public Port? VolumePort { get; } public Pipe1D Pipe { get; } public bool IsPipeLeftEnd { get; } public Func AreaProvider { get; set; } public double DischargeCoefficient { get; set; } = 0.62; public double EffectiveLength { get; set; } = 0.001; public bool UseInertance { get; set; } = true; private double _mdot; // positive = volume → pipe 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 OrificeLink(Port? volumePort, Pipe1D pipe, bool isPipeLeftEnd, Func areaProvider) { VolumePort = volumePort; Pipe = pipe ?? throw new ArgumentNullException(nameof(pipe)); IsPipeLeftEnd = isPipeLeftEnd; AreaProvider = areaProvider ?? throw new ArgumentNullException(nameof(areaProvider)); _mdot = 0.0; } public void Resolve(double dtSub) { double area = AreaProvider(); if (area < 1e-12 || VolumePort == null) { SetClosedWall(); return; } // Gather states double volP = VolumePort.Pressure; double volRho = VolumePort.Density; double volT = VolumePort.Temperature; double volH = VolumePort.SpecificEnthalpy; (double pipeRho, double pipeU, double pipeP) = IsPipeLeftEnd ? Pipe.GetInteriorStateLeft() : Pipe.GetInteriorStateRight(); double pipeT = pipeP / Math.Max(pipeRho * 287.0, 1e-12); double gamma = 1.4; double R = 287.0; // ---- 1. Steady‑state nozzle solution (gives correct exit pressure) ---- double mdotSS; double rhoFace0, uFace0, pFace0; if (volP >= pipeP) { IsentropicOrifice.Compute(volP, volRho, volT, pipeP, gamma, R, area, DischargeCoefficient, out double mdotUpToDown, out rhoFace0, out uFace0, out pFace0); mdotSS = mdotUpToDown; // volume → pipe } else { IsentropicOrifice.Compute(pipeP, pipeRho, pipeT, volP, gamma, R, area, DischargeCoefficient, out double mdotUpToDown, out rhoFace0, out uFace0, out pFace0); mdotSS = -mdotUpToDown; // pipe → volume → negative for volume→pipe convention } // ---- 2. Inertance dynamics ---- if (UseInertance) { double rhoUp = _mdot >= 0 ? volRho : pipeRho; double inertance = rhoUp * EffectiveLength / area; double dp = volP - pipeP; double resistance = Math.Abs(dp) / Math.Max(Math.Abs(mdotSS), 1e-12); double dmdot_dt = (dp - resistance * _mdot) / inertance; _mdot += dmdot_dt * dtSub; } else { _mdot = mdotSS; } // Clamp outflow to available mass if (VolumePort.Owner is Volume0D vol) { double maxOut = vol.Mass / dtSub; if (_mdot > maxOut) _mdot = maxOut; } // ---- 3. Ghost state (use nozzle‑exit pressure!) ---- double rhoFace = _mdot >= 0 ? volRho : pipeRho; // upstream density double pFace = pFace0; // correct exit pressure (choked/subsonic) double mdotMag = Math.Abs(_mdot); double uFace = mdotMag / (rhoFace * area); if (IsPipeLeftEnd) uFace = _mdot >= 0 ? uFace : -uFace; // left: +u into pipe else uFace = _mdot >= 0 ? -uFace : uFace; // right: +u out of pipe if (IsPipeLeftEnd) Pipe.SetGhostLeft(rhoFace, uFace, pFace); else Pipe.SetGhostRight(rhoFace, uFace, pFace); // Store for monitoring double mdotIntoVolume = -_mdot; LastMassFlowRate = mdotIntoVolume; LastFaceDensity = rhoFace; LastFaceVelocity = uFace; LastFacePressure = pFace; VolumePort.MassFlowRate = mdotIntoVolume; if (mdotIntoVolume >= 0) { double hPipe = gamma / (gamma - 1.0) * pipeP / Math.Max(pipeRho, 1e-12); VolumePort.SpecificEnthalpy = hPipe; } else { VolumePort.SpecificEnthalpy = volH; } } private void SetClosedWall() { var (rInt, uInt, pInt) = IsPipeLeftEnd ? Pipe.GetInteriorStateLeft() : Pipe.GetInteriorStateRight(); if (IsPipeLeftEnd) Pipe.SetGhostLeft(rInt, -uInt, pInt); else Pipe.SetGhostRight(rInt, -uInt, pInt); LastMassFlowRate = 0.0; LastFaceDensity = rInt; LastFaceVelocity = 0.0; LastFacePressure = pInt; if (VolumePort != null) VolumePort.MassFlowRate = 0.0; } } }