Fixed orifice with inertia, automatic R value

This commit is contained in:
2026-05-09 14:43:49 +02:00
parent 1489f278dc
commit a9e1c966b5
5 changed files with 223 additions and 173 deletions

View File

@@ -25,7 +25,8 @@ namespace FluidSim.Core
public float EffectiveLength;
public float CurrentMdot; // kg/s, positive = volume → pipe
// --- Loss coefficient (linear resistance) ---
// --- Loss coefficient (linear resistance) inertance only ---
// If 0 when UseInertance is true, a stable default is autocomputed at runtime.
public float LossCoefficient; // N·s/m⁵ or kg/(m⁴·s)
}
@@ -57,9 +58,10 @@ namespace FluidSim.Core
public int OpenEndCount { get; private set; }
// ---------- Add orifice (no inertance) ----------
// Simple isentropic nozzle no builtin loss. For dissipation use pipe damping
// or the inertance model if you need a damped resonator.
public void AddOrifice(Port volumePort, int pipeIndex, bool isLeftEnd,
int areaIndex, float dischargeCoeff = 1f,
float lossCoefficient = 0f)
int areaIndex, float dischargeCoeff = 1f)
{
_orifices[OrificeCount] = new OrificeDesc
{
@@ -71,22 +73,24 @@ namespace FluidSim.Core
UseInertance = false,
EffectiveLength = 0f,
CurrentMdot = 0f,
LossCoefficient = lossCoefficient
LossCoefficient = 0f
};
OrificeCount++;
}
// ---------- Add orifice with inertance ----------
// effectiveLength length of the inertial slug (m), typically the physical neck length.
// lossCoefficient linear resistance (N·s/m⁵). If 0 (or omitted) an automatic stable
// value will be computed from the pipe's characteristic impedance.
public void AddOrificeWithInertance(Port volumePort, int pipeIndex, bool isLeftEnd,
int areaIndex, float dischargeCoeff,
float effectiveLength, float lossCoefficient = 0f)
{
// Reuse the base AddOrifice and then override fields
AddOrifice(volumePort, pipeIndex, isLeftEnd, areaIndex, dischargeCoeff, lossCoefficient);
AddOrifice(volumePort, pipeIndex, isLeftEnd, areaIndex, dischargeCoeff);
ref var d = ref _orifices[OrificeCount - 1];
d.UseInertance = true;
d.EffectiveLength = effectiveLength;
d.LossCoefficient = lossCoefficient; // store the linear resistance
d.LossCoefficient = lossCoefficient;
}
public void AddOpenEnd(int pipeIndex, bool isLeftEnd,
@@ -146,7 +150,7 @@ namespace FluidSim.Core
? _pipeSystem.GetInteriorAirFractionLeft(d.PipeIndex)
: _pipeSystem.GetInteriorAirFractionRight(d.PipeIndex);
// ---- Handle closed orifice (area ≈ 0) as a wall ----
// ---- Handle closed orifice as a wall ----
if (area < 1e-12f || d.VolumePort == null)
{
var (rInt, uInt, pInt) = d.IsLeftEnd
@@ -165,7 +169,7 @@ namespace FluidSim.Core
continue;
}
// ---- Preliminary isentropic solution (used for face pressure if inertance is on) ----
// ---- Preliminary isentropic solution (for reference) ----
float mdotEst, rhoFaceEst, uFaceEst, pFaceEst;
if (volP >= pipeP)
{
@@ -184,16 +188,26 @@ namespace FluidSim.Core
if (d.UseInertance)
{
// ---- Inertance ODE with (possibly automatic) linear loss ----
float rhoUp = d.CurrentMdot >= 0 ? volRho : pipeRho;
float inertance = rhoUp * d.EffectiveLength / MathF.Max(area, 1e-12f);
float dp = volP - pipeP;
float Rlin = d.LossCoefficient;
// Forward Euler
// If loss coefficient not provided, use a tiny fraction of the pipe's characteristic impedance
float Rlin = d.LossCoefficient;
if (Rlin <= 0f)
{
// Autosized linear drag: 0.5% of Z_char
float rhoRef = d.CurrentMdot >= 0 ? volRho : pipeRho;
float cRef = d.CurrentMdot >= 0 ? MathF.Sqrt(Gamma * Rgas * volT) : MathF.Sqrt(Gamma * Rgas * pipeT);
float Z_char = rhoRef * cRef / MathF.Max(area, 1e-12f);
Rlin = 0.005f * Z_char;
}
float dmdot_dt = (dp - Rlin * d.CurrentMdot) / MathF.Max(inertance, 1e-12f);
float mdotNew = d.CurrentMdot + dmdot_dt * dt;
// ---------- Symmetric flow limiters (existing) ----------
// Symmetric flow limiters
if (d.VolumePort.Owner is Volume0D vol0)
{
float maxOut = vol0.Mass / dt;
@@ -209,7 +223,7 @@ namespace FluidSim.Core
float maxFromPipe = pipeCellMass / dt;
if (mdotNew < -maxFromPipe) mdotNew = -maxFromPipe;
// ---------- Velocity clamp to Mach 0.9 ----------
// Velocity clamp Mach 0.9
float rhoFacePrelim = mdotNew >= 0 ? volRho : pipeRho;
float uFacePrelim = MathF.Abs(mdotNew) / MathF.Max(rhoFacePrelim * area, 1e-12f);
float cUp = mdotNew >= 0 ? MathF.Sqrt(Gamma * Rgas * volT) : MathF.Sqrt(Gamma * Rgas * pipeT);
@@ -220,20 +234,18 @@ namespace FluidSim.Core
mdotNew = rhoFacePrelim * uFacePrelim * area * (mdotNew >= 0 ? 1f : -1f);
}
// NaN safety
if (float.IsNaN(mdotNew)) mdotNew = 0f;
d.CurrentMdot = mdotNew;
mdotFinal = mdotNew;
// Ghost state
rhoFace = mdotFinal >= 0 ? volRho : pipeRho;
pFace = pFaceEst;
uFace = MathF.Abs(mdotFinal) / MathF.Max(rhoFace * area, 1e-12f);
}
else
{
// ---- Standard quasisteady orifice ----
// ---- Standard quasisteady orifice (purely isentropic) ----
mdotFinal = mdotEst;
rhoFace = rhoFaceEst;
uFace = uFaceEst;
@@ -245,6 +257,17 @@ namespace FluidSim.Core
float maxOut = vol0.Mass / dt;
if (mdotFinal > maxOut) mdotFinal = maxOut;
}
// Safety velocity clamp (Mach 0.9)
float cLocal = mdotFinal >= 0 ? MathF.Sqrt(Gamma * Rgas * volT) : MathF.Sqrt(Gamma * Rgas * pipeT);
float maxULocal = 0.9f * cLocal;
float uCheck = MathF.Abs(mdotFinal) / MathF.Max(rhoFace * area, 1e-12f);
if (uCheck > maxULocal)
{
uFace = maxULocal;
mdotFinal = rhoFace * uFace * area * (mdotFinal >= 0 ? 1f : -1f);
}
d.CurrentMdot = mdotFinal;
}
@@ -271,18 +294,17 @@ namespace FluidSim.Core
else
_pipeSystem.SetGhostRight(d.PipeIndex, rhoFace, uFace, pFace, airFracGhost);
// ---- Update volume port (mass flow: positive into volume) ----
// ---- Update volume port ----
if (d.VolumePort != null)
{
d.VolumePort.MassFlowRate = -mdotFinal;
// Set enthalpy of the stream entering the volume
if (-mdotFinal >= 0) // mass flowing into the volume (out of pipe)
if (-mdotFinal >= 0) // mass flowing into the volume
{
float pipeH = GammaOverGm1 * pipeP / MathF.Max(pipeRho, 1e-12f);
d.VolumePort.SpecificEnthalpy = pipeH;
}
else // mass flowing out of the volume (into pipe)
else // mass flowing out of the volume
{
d.VolumePort.SpecificEnthalpy = volH;
}