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. /// The area can be dynamic (Func). /// 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 Gamma { get; set; } = 1.4; public double GasConstant { get; set; } = 287.0; // 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 OrificeLink(Port volumePort, Pipe1D pipe, bool isPipeLeftEnd, Func areaProvider) { VolumePort = volumePort ?? throw new ArgumentNullException(nameof(volumePort)); Pipe = pipe ?? throw new ArgumentNullException(nameof(pipe)); IsPipeLeftEnd = isPipeLeftEnd; AreaProvider = areaProvider ?? throw new ArgumentNullException(nameof(areaProvider)); } /// /// Resolve the coupling for one sub‑step. Computes nozzle flow (isentropic) /// and sets the pipe ghost cell and the port flow rates. /// public void Resolve(double dtSub) { double area = AreaProvider(); if (area < 1e-12) { SetClosedWall(); return; } // Retrieve volume state double volP = VolumePort.Pressure; double volRho = VolumePort.Density; double volT = VolumePort.Temperature; double volH = VolumePort.SpecificEnthalpy; // Retrieve pipe interior state at the connected end (double pipeRho, double pipeU, double pipeP) = IsPipeLeftEnd ? Pipe.GetInteriorStateLeft() : Pipe.GetInteriorStateRight(); // Determine upstream/downstream: if volume pressure > pipe pressure, flow is out of volume (negative into volume). bool flowOutOfVolume = volP > pipeP; double pUp, rhoUp, TUp, pDown; if (flowOutOfVolume) { pUp = volP; rhoUp = volRho; TUp = volT; pDown = pipeP; } else { // Pipe is upstream pUp = pipeP; rhoUp = pipeRho; TUp = pipeP / (pipeRho * GasConstant); // temperature from pipe pDown = volP; } // Compute isentropic nozzle flow IsentropicOrifice.Compute(pUp, rhoUp, TUp, Gamma, GasConstant, pDown, area, DischargeCoefficient, out double mdotUpstreamToDown, out double rhoFace, out double uFace, out double pFace); // mdotUpstreamToDown is positive from upstream to downstream. // Convert to mass flow into volume (positive mdot = into volume). double mdotVolume; if (flowOutOfVolume) mdotVolume = -mdotUpstreamToDown; // out of volume is negative else mdotVolume = mdotUpstreamToDown; // into volume is positive // Clamp mass flow to available mass in volume (if it is a Volume0D) if (VolumePort.Owner is Volume0D vol) { double maxMdot = vol.Mass / dtSub; if (mdotVolume > maxMdot) mdotVolume = maxMdot; if (mdotVolume < -maxMdot) mdotVolume = -maxMdot; } // Apply ghost state to pipe if (IsPipeLeftEnd) Pipe.SetGhostLeft(rhoFace, uFace, pFace); else Pipe.SetGhostRight(rhoFace, uFace, pFace); // Store results LastMassFlowRate = mdotVolume; LastFaceDensity = rhoFace; LastFaceVelocity = uFace; LastFacePressure = pFace; // Set port flow rates for volume integration VolumePort.MassFlowRate = mdotVolume; if (mdotVolume >= 0) { // Inflow: enthalpy comes from upstream (pipe) double pPipe = pipeP; double rhoPipe = pipeRho; VolumePort.SpecificEnthalpy = Gamma / (Gamma - 1.0) * pPipe / rhoPipe; } else { // Outflow: volume's own specific 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; VolumePort.MassFlowRate = 0.0; // Keep specific enthalpy as is (not used) } } }