Files
FluidSim/Core/OrificeLink.cs
2026-05-07 12:55:57 +02:00

168 lines
6.4 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
{
/// <summary>
/// Connects a port (volume or atmosphere) to one end of a pipe via an orifice.
/// Uses the isentropic nozzle model for the steadystate relationship,
/// and includes acoustic inertance for dynamic (Helmholtz) behaviour.
/// </summary>
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;
// 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 steadystate 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<double> 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;
// ---- Steadystate 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;
}
}
}