104 lines
4.0 KiB
C#
104 lines
4.0 KiB
C#
using System;
|
||
using FluidSim.Components;
|
||
|
||
namespace FluidSim.Core
|
||
{
|
||
/// <summary>
|
||
/// Characteristic open‑end boundary condition after Jones (1978).
|
||
/// For all subsonic flow (outflow and inflow), the ghost state is derived
|
||
/// from the isentropic expansion to ambient pressure, using the pipe's entropy,
|
||
/// and the outgoing Riemann invariant. This avoids a density jump at flow reversal.
|
||
/// Supersonic outflow extrapolates the interior state.
|
||
/// </summary>
|
||
public class OpenEndLink
|
||
{
|
||
public Pipe1D Pipe { get; }
|
||
public bool IsLeftEnd { get; }
|
||
public double AmbientPressure { get; set; } = 101325.0;
|
||
public double Gamma { get; set; } = 1.4;
|
||
|
||
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 OpenEndLink(Pipe1D pipe, bool isLeftEnd)
|
||
{
|
||
Pipe = pipe ?? throw new ArgumentNullException(nameof(pipe));
|
||
IsLeftEnd = isLeftEnd;
|
||
}
|
||
|
||
public void Resolve(double dtSub)
|
||
{
|
||
(double rhoInt, double uInt, double pInt) = IsLeftEnd
|
||
? Pipe.GetInteriorStateLeft()
|
||
: Pipe.GetInteriorStateRight();
|
||
|
||
double gamma = Gamma;
|
||
double gm1 = gamma - 1.0;
|
||
double cInt = Math.Sqrt(gamma * pInt / Math.Max(rhoInt, 1e-12));
|
||
double pAmb = AmbientPressure;
|
||
|
||
// Riemann invariants
|
||
double J_plus = uInt + 2.0 * cInt / gm1;
|
||
double J_minus = uInt - 2.0 * cInt / gm1;
|
||
|
||
double rhoGhost, uGhost, pGhost;
|
||
|
||
// ---- Subsonic branch (used for both outflow and inflow) ----
|
||
// Isentropic expansion to ambient pressure using pipe's entropy
|
||
double s = pInt / Math.Pow(rhoInt, gamma); // entropy constant
|
||
double rhoIso = Math.Pow(pAmb / s, 1.0 / gamma);
|
||
double cIso = Math.Sqrt(gamma * pAmb / Math.Max(rhoIso, 1e-12));
|
||
double uIso = IsLeftEnd
|
||
? (J_minus + 2.0 * cIso / gm1)
|
||
: (J_plus - 2.0 * cIso / gm1);
|
||
|
||
// Check for supersonic outflow: if the isentropic velocity exceeds the speed of sound,
|
||
// the flow is supersonic and we extrapolate the interior state.
|
||
bool supersonic = IsLeftEnd
|
||
? (uInt <= -cInt) // left end: outflow is when u < -c
|
||
: (uInt >= cInt); // right end: outflow is when u > c
|
||
|
||
// Extra check: if the isentropic velocity is supersonic in the outflow direction,
|
||
// also treat as supersonic (this can happen when the interior pressure is very high).
|
||
if (!supersonic)
|
||
{
|
||
if (IsLeftEnd)
|
||
supersonic = uIso <= -cIso;
|
||
else
|
||
supersonic = uIso >= cIso;
|
||
}
|
||
|
||
if (supersonic)
|
||
{
|
||
// Supersonic outflow – extrapolate interior
|
||
rhoGhost = rhoInt;
|
||
uGhost = uInt;
|
||
pGhost = pInt;
|
||
}
|
||
else
|
||
{
|
||
// Subsonic flow – use the isentropic state
|
||
rhoGhost = rhoIso;
|
||
uGhost = uIso;
|
||
pGhost = pAmb;
|
||
}
|
||
|
||
// Apply ghost to pipe
|
||
if (IsLeftEnd)
|
||
Pipe.SetGhostLeft(rhoGhost, uGhost, pGhost);
|
||
else
|
||
Pipe.SetGhostRight(rhoGhost, uGhost, pGhost);
|
||
|
||
// Mass flow out of the pipe (positive = leaving)
|
||
double area = Pipe.Area;
|
||
double mdot = rhoGhost * uGhost * area;
|
||
if (IsLeftEnd) mdot = -mdot; // left end: positive u is into pipe, so out is -u
|
||
LastMassFlowRate = mdot;
|
||
LastFaceDensity = rhoGhost;
|
||
LastFaceVelocity = uGhost;
|
||
LastFacePressure = pGhost;
|
||
}
|
||
}
|
||
} |