using System; using FluidSim.Components; using FluidSim.Interfaces; namespace FluidSim.Core { /// /// Connects a port (volume or atmosphere) to one end of a pipe via an orifice. /// Uses the isentropic nozzle model for the steady‑state relationship, /// and includes acoustic inertance for dynamic (Helmholtz) behaviour. /// 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; // Acoustic length (wall thickness + end correction) – controls the resonance frequency public double EffectiveLength { get; set; } = 0.001; // 1 mm // Whether to include inertance; if false, uses the steady‑state nozzle model directly public bool UseInertance { get; set; } = true; // Current mass flow (kg/s, positive = volume → pipe) private double _mdot; 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; // null is allowed 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(); // Closed wall or missing volume port => reflective boundary if (area < 1e-12 || VolumePort == null) { SetClosedWall(); return; } // Gather volume state double volP = VolumePort.Pressure; double volRho = VolumePort.Density; double volT = VolumePort.Temperature; double volH = VolumePort.SpecificEnthalpy; // Gather pipe interior state at the connected end (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; // ---- Steady‑state mass flow from isentropic nozzle ---- double mdotSS; // positive = volume → pipe double rhoFace, uFace, pFace; if (volP >= pipeP) { IsentropicOrifice.Compute(volP, volRho, volT, pipeP, gamma, R, area, DischargeCoefficient, out double mdotUpToDown, out rhoFace, out uFace, out pFace); mdotSS = mdotUpToDown; // volume → pipe } else { IsentropicOrifice.Compute(pipeP, pipeRho, pipeT, volP, gamma, R, area, DischargeCoefficient, out double mdotUpToDown, out rhoFace, out uFace, out pFace); mdotSS = -mdotUpToDown; // pipe → volume → negative for volume→pipe convention } // ---- Inertance ODE (optional) ---- 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 ---- // Density = upstream density (consistent with current flow direction) rhoFace = _mdot >= 0 ? volRho : pipeRho; // Pressure = downstream pressure (consistent with nozzle exit) pFace = _mdot >= 0 ? pipeP : volP; // Velocity magnitude derived from actual mass flow double mdotMag = Math.Abs(_mdot); uFace = mdotMag / (rhoFace * area); if (IsPipeLeftEnd) uFace = _mdot >= 0 ? uFace : -uFace; // left end: positive u = into pipe else uFace = _mdot >= 0 ? -uFace : uFace; // right end: positive u = out of pipe // Apply ghost to pipe if (IsPipeLeftEnd) Pipe.SetGhostLeft(rhoFace, uFace, pFace); else Pipe.SetGhostRight(rhoFace, uFace, pFace); // ---- Store results ---- double mdotIntoVolume = -_mdot; // positive = into volume LastMassFlowRate = mdotIntoVolume; LastFaceDensity = rhoFace; LastFaceVelocity = uFace; LastFacePressure = pFace; VolumePort.MassFlowRate = mdotIntoVolume; // Enthalpy for volume integration if (mdotIntoVolume >= 0) // inflow → pipe enthalpy { double hPipe = gamma / (gamma - 1.0) * pipeP / Math.Max(pipeRho, 1e-12); VolumePort.SpecificEnthalpy = hPipe; } else // outflow → volume's own enthalpy { 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; // Don't touch VolumePort if it's null if (VolumePort != null) VolumePort.MassFlowRate = 0.0; } } }