Files
FluidSim/Core/OrificeLink.cs
2026-05-07 21:48:37 +02:00

173 lines
6.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<double> 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<double> 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;
// ---- Steadystate 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;
}
}
}