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; } = false; // Current mass flow (kg/s, positive = volume → pipe) private double _mdot; public double LastMassFlowRate { get; private set; } // positive = into volume 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 volAF = VolumePort.AirFraction; (double pipeRho, double pipeU, double pipeP) = IsPipeLeftEnd ? Pipe.GetInteriorStateLeft() : Pipe.GetInteriorStateRight(); double pipeT = pipeP / Math.Max(pipeRho * 287.0, 1e-12); double pipeAF = IsPipeLeftEnd ? Pipe.GetInteriorAirFractionLeft() : Pipe.GetInteriorAirFractionRight(); double gamma = 1.4; double R = 287.0; // ---- Steady‑state nozzle solution ---- double mdotSS; // positive = volume → pipe 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; } else { IsentropicOrifice.Compute(pipeP, pipeRho, pipeT, volP, gamma, R, area, DischargeCoefficient, out double mdotUpToDown, out rhoFace0, out uFace0, out pFace0); mdotSS = -mdotUpToDown; } // ---- Dynamic update ---- 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 finite volume) if (VolumePort.Owner is Volume0D vol) { double maxOut = vol.Mass / dtSub; if (_mdot > maxOut) _mdot = maxOut; } // ---- Ghost state with air fraction ---- double rhoFace = _mdot >= 0 ? volRho : pipeRho; double pFace = pFace0; double mdotMag = Math.Abs(_mdot); double uFace = mdotMag / (rhoFace * area); // Determine air fraction for ghost and for volume port double airFracGhost; // air fraction of ghost cell (at pipe end) double airFracForVolume; // if flow reverses into volume, this is the air fraction entering volume if (_mdot >= 0) // volume → pipe { airFracGhost = volAF; // Flow enters pipe; no need to set volume's air fraction (port already has its own) airFracForVolume = volAF; // unused } else // pipe → volume { airFracGhost = pipeAF; airFracForVolume = pipeAF; VolumePort.AirFraction = airFracForVolume; } if (IsPipeLeftEnd) uFace = _mdot >= 0 ? uFace : -uFace; else uFace = _mdot >= 0 ? -uFace : uFace; if (IsPipeLeftEnd) Pipe.SetGhostLeft(rhoFace, uFace, pFace, airFracGhost); else Pipe.SetGhostRight(rhoFace, uFace, pFace, airFracGhost); // Store results (positive = into volume) LastMassFlowRate = -_mdot; LastFaceDensity = rhoFace; LastFaceVelocity = uFace; LastFacePressure = pFace; VolumePort.MassFlowRate = -_mdot; // Enthalpy transport if (-_mdot >= 0) // inflow → pipe enthalpy { 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, IsPipeLeftEnd ? Pipe.GetInteriorAirFractionLeft() : Pipe.GetInteriorAirFractionRight()); else Pipe.SetGhostRight(rInt, -uInt, pInt, IsPipeLeftEnd ? Pipe.GetInteriorAirFractionLeft() : Pipe.GetInteriorAirFractionRight()); LastMassFlowRate = 0.0; LastFaceDensity = rInt; LastFaceVelocity = 0.0; LastFacePressure = pInt; if (VolumePort != null) VolumePort.MassFlowRate = 0.0; } } }