insane engine sound

This commit is contained in:
2026-05-05 11:24:32 +02:00
parent d963032e74
commit f16a1aa763
4 changed files with 666 additions and 316 deletions

View File

@@ -1,4 +1,5 @@
using System;
using System.Numerics;
using FluidSim.Interfaces;
namespace FluidSim.Components
@@ -6,7 +7,7 @@ namespace FluidSim.Components
public enum BoundaryType
{
OpenEnd,
ZeroPressureOpen, // pressurerelease boundary (strong reflection)
ZeroPressureOpen,
ClosedEnd,
GhostCell
}
@@ -18,72 +19,99 @@ namespace FluidSim.Components
public double Area => _area;
public double DampingMultiplier { get; set; } = 1.0;
private int _n;
private double _dx, _dt, _gamma, _area, _diameter;
private double[] _rho, _rhou, _E;
private int _n; // number of real cells
private float _dx, _dt; // spatial step, global time step
private float _area, _diameter; // crosssection, diameter
private float _gamma; // ratio of specific heats (1.4)
// Conserved variables arrays sized [_n] (only real cells, ghosts handled externally)
private float[] _rho;
private float[] _rhou;
private float[] _E;
// Flux arrays for faces 0 .. _n (face i is between cell i-1 and i)
private float[] _fluxM; // mass flux
private float[] _fluxP; // momentum flux
private float[] _fluxE; // energy flux
// Ghost cell states
private double _rhoGhostL, _uGhostL, _pGhostL;
private double _rhoGhostR, _uGhostR, _pGhostR;
private float _rhoGhostL, _uGhostL, _pGhostL;
private float _rhoGhostR, _uGhostR, _pGhostR;
private bool _ghostLSet, _ghostRSet;
private BoundaryType _aBCType = BoundaryType.GhostCell;
private BoundaryType _bBCType = BoundaryType.GhostCell;
private double _aAmbientPressure = 101325.0;
private double _bAmbientPressure = 101325.0;
private float _aAmbientPressure = 101325f;
private float _bAmbientPressure = 101325f;
private const double CflTarget = 0.8;
private const double ReferenceSoundSpeed = 340.0;
private double _lastMassFlow = 0.0;
// CFL / sub-stepping
private const float CflTarget = 0.8f;
private const float ReferenceSoundSpeed = 340f;
private float _lastMassFlow = 0f;
// Precomputed for damping
private float _laminarCoeff; // 8*mu / r^2, then multiplied by DampingMultiplier
public Pipe1D(double length, double area, int sampleRate, int forcedCellCount = 0)
{
double dtGlobal = 1.0 / sampleRate;
float dtGlobal = 1f / sampleRate;
int nCells;
float dxTarget = ReferenceSoundSpeed * dtGlobal / CflTarget;
if (forcedCellCount > 1)
nCells = forcedCellCount;
else
{
double dxTarget = ReferenceSoundSpeed * dtGlobal * CflTarget;
nCells = Math.Max(2, (int)Math.Round(length / dxTarget, MidpointRounding.AwayFromZero));
while (length / nCells > dxTarget * 1.01 && nCells < int.MaxValue - 1)
nCells = Math.Max(2, (int)Math.Round((float)length / dxTarget, MidpointRounding.AwayFromZero));
while (length / nCells > dxTarget * 1.01f && nCells < int.MaxValue - 1)
nCells++;
}
_n = nCells;
_dx = length / _n;
_dx = (float)(length / nCells);
_dt = dtGlobal;
_area = area;
_gamma = 1.4;
_diameter = 2.0 * Math.Sqrt(area / Math.PI);
_area = (float)area;
_diameter = (float)(2.0 * Math.Sqrt(area / Math.PI));
_gamma = 1.4f;
_rho = new double[_n];
_rhou = new double[_n];
_E = new double[_n];
_rho = new float[_n];
_rhou = new float[_n];
_E = new float[_n];
// +1 because there are _n+1 faces
_fluxM = new float[_n + 1];
_fluxP = new float[_n + 1];
_fluxE = new float[_n + 1];
// Precompute laminar damping coefficient (using air at 20°C)
float mu_air = 1.8e-5f;
float radius = _diameter * 0.5f;
_laminarCoeff = 8f * mu_air / (radius * radius); // will be multiplied by DampingMultiplier at each step
PortA = new Port();
PortB = new Port();
}
// ==================== PUBLIC API (unchanged) ============================
public void SetABoundaryType(BoundaryType type) => _aBCType = type;
public void SetBBoundaryType(BoundaryType type) => _bBCType = type;
public void SetAAmbientPressure(double p) => _aAmbientPressure = p;
public void SetBAmbientPressure(double p) => _bAmbientPressure = p;
public void SetAAmbientPressure(double p) => _aAmbientPressure = (float)p;
public void SetBAmbientPressure(double p) => _bAmbientPressure = (float)p;
public void SetGhostLeft(double rho, double u, double p)
{
_rhoGhostL = rho;
_uGhostL = u;
_pGhostL = p;
_rhoGhostL = (float)rho;
_uGhostL = (float)u;
_pGhostL = (float)p;
_ghostLSet = true;
}
public void SetGhostRight(double rho, double u, double p)
{
_rhoGhostR = rho;
_uGhostR = u;
_pGhostR = p;
_rhoGhostR = (float)rho;
_uGhostR = (float)u;
_pGhostR = (float)p;
_ghostRSet = true;
}
public void ClearGhostFlag()
@@ -94,39 +122,73 @@ namespace FluidSim.Components
public void SetUniformState(double rho, double u, double p)
{
double e = p / ((_gamma - 1) * rho);
double Etot = rho * e + 0.5 * rho * u * u;
float r = (float)rho;
float vel = (float)u;
float pr = (float)p;
float e = pr / ((_gamma - 1f) * r);
float Etot = r * e + 0.5f * r * vel * vel;
for (int i = 0; i < _n; i++)
{
_rho[i] = rho;
_rhou[i] = rho * u;
_rho[i] = r;
_rhou[i] = r * vel;
_E[i] = Etot;
}
}
public int GetCellCount() => _n;
public double GetCellDensity(int i) => _rho[i];
public double GetCellVelocity(int i)
{
float rho = Math.Max(_rho[i], 1e-12f);
return _rhou[i] / rho;
}
public double GetCellPressure(int i)
{
float rho = Math.Max(_rho[i], 1e-12f);
return (_gamma - 1f) * (_E[i] - 0.5f * _rhou[i] * _rhou[i] / rho);
}
public double GetPressureAtFraction(double fraction)
{
int i = (int)(fraction * (_n - 1));
i = Math.Clamp(i, 0, _n - 1);
return GetCellPressure(i);
}
public void SetCellState(int i, double rho, double u, double p)
{
if (i < 0 || i >= _n) return;
float r = (float)rho;
float vel = (float)u;
float pr = (float)p;
_rho[i] = r;
_rhou[i] = r * vel;
float e = pr / ((_gamma - 1f) * r);
_E[i] = r * e + 0.5f * r * vel * vel;
}
public double GetOpenEndMassFlow()
{
if (_bBCType != BoundaryType.OpenEnd && _bBCType != BoundaryType.ZeroPressureOpen)
return 0.0;
int lastCell = _n - 1;
double rho = _rho[lastCell];
double u = _rhou[lastCell] / Math.Max(rho, 1e-12);
double p = Pressure(lastCell);
float rho = _rho[lastCell];
float u = _rhou[lastCell] / Math.Max(rho, 1e-12f);
float p = PressureScalar(lastCell);
double c = Math.Sqrt(_gamma * p / rho);
double uFace = u;
double rhoFace = rho;
double pFace = p;
float c = MathF.Sqrt(_gamma * p / rho);
float uFace = u;
float rhoFace = rho;
float pFace = p;
// Subsonic outflow: impose ambient pressure, adjust velocity and density
if (uFace > 0 && uFace < c)
if (uFace > 0 && uFace < c) // subsonic outflow
{
double s = p / Math.Pow(rho, _gamma);
double rhoAmb = Math.Pow(_bAmbientPressure / s, 1.0 / _gamma);
double cAmb = Math.Sqrt(_gamma * _bAmbientPressure / rhoAmb);
double J_plus = u + 2.0 * c / (_gamma - 1.0);
double uFaceNew = J_plus - 2.0 * cAmb / (_gamma - 1.0);
float s = p / MathF.Pow(rho, _gamma);
float rhoAmb = MathF.Pow(_bAmbientPressure / s, 1f / _gamma);
float cAmb = MathF.Sqrt(_gamma * _bAmbientPressure / rhoAmb);
float J_plus = u + 2f * c / (_gamma - 1f);
float uFaceNew = J_plus - 2f * cAmb / (_gamma - 1f);
if (uFaceNew > 0) uFace = uFaceNew;
if (uFace < 0) uFace = 0;
rhoFace = rhoAmb;
@@ -138,229 +200,410 @@ namespace FluidSim.Components
public double GetAndStoreMassFlowForDerivative()
{
double current = GetOpenEndMassFlow();
float current = (float)GetOpenEndMassFlow();
double derivative = (current - _lastMassFlow) / _dt;
_lastMassFlow = current;
return derivative;
}
public void SetCellState(int i, double rho, double u, double p)
public int GetRequiredSubSteps(double dtGlobal, double cflTarget = 0.8f)
{
if (i < 0 || i >= _n) return;
_rho[i] = rho;
_rhou[i] = rho * u;
double e = p / ((_gamma - 1) * rho);
_E[i] = rho * e + 0.5 * rho * u * u;
}
public int GetCellCount() => _n;
public double GetCellDensity(int i) => _rho[i];
public double GetCellPressure(int i) => Pressure(i);
public double GetCellVelocity(int i) => _rhou[i] / Math.Max(_rho[i], 1e-12);
private double Pressure(int i) => (_gamma - 1.0) * (_E[i] - 0.5 * _rhou[i] * _rhou[i] / Math.Max(_rho[i], 1e-12));
public double GetPressureAtFraction(double fraction)
{
int i = (int)(fraction * (_n - 1));
i = Math.Clamp(i, 0, _n - 1);
return Pressure(i);
}
public int GetRequiredSubSteps(double dtGlobal, double cflTarget = 0.8)
{
double maxW = 0.0;
float maxW = 0f;
for (int i = 0; i < _n; i++)
{
double rho = _rho[i];
double u = Math.Abs(_rhou[i] / Math.Max(rho, 1e-12));
double c = Math.Sqrt(_gamma * Pressure(i) / Math.Max(rho, 1e-12));
double local = u + c;
float rho = _rho[i];
float u = MathF.Abs(_rhou[i] / Math.Max(rho, 1e-12f));
float p = PressureScalar(i);
float c = MathF.Sqrt(_gamma * p / Math.Max(rho, 1e-12f));
float local = u + c;
if (local > maxW) maxW = local;
}
maxW = Math.Max(maxW, 1e-8);
return Math.Max(1, (int)Math.Ceiling(dtGlobal * maxW / (cflTarget * _dx)));
maxW = Math.Max(maxW, 1e-8f);
return Math.Max(1, (int)Math.Ceiling((float)dtGlobal * maxW / ((float)cflTarget * _dx)));
}
// ==================== MAIN SIMULATION ==================================
public void SimulateSingleStep(double dtSub)
{
float dt = (float)dtSub;
int n = _n;
double[] Fm = new double[n + 1];
double[] Fp = new double[n + 1];
double[] Fe = new double[n + 1];
// Left boundary (face 0)
double rhoL = _rho[0];
double uL = _rhou[0] / Math.Max(rhoL, 1e-12);
double pL = Pressure(0);
if (_aBCType == BoundaryType.GhostCell && _ghostLSet)
HLLCFlux(_rhoGhostL, _uGhostL, _pGhostL, rhoL, uL, pL, out Fm[0], out Fp[0], out Fe[0]);
else if (_aBCType == BoundaryType.OpenEnd)
OpenEndFluxLeft(rhoL, uL, pL, _aAmbientPressure, out Fm[0], out Fp[0], out Fe[0]);
else if (_aBCType == BoundaryType.ZeroPressureOpen)
// --- 1. Left boundary face (index 0) scalar -----------------------
float rhoL = _rho[0];
float uL = _rhou[0] / Math.Max(rhoL, 1e-12f);
float pL = PressureScalar(0);
ComputeLeftBoundaryFlux(rhoL, uL, pL, out _fluxM[0], out _fluxP[0], out _fluxE[0]);
// --- 2. Internal faces (1 .. n-1) SIMD ---------------------------
int vectorSize = Vector<float>.Count;
int lastSimdFace = n - vectorSize; // highest face index that starts a full vector block
// Face index f is between cell f-1 (left) and cell f (right).
// We want to cover faces 1..n-1.
for (int f = 1; f <= lastSimdFace; f += vectorSize)
{
// Strong reflection: force pressure to ambient, extrapolate density and velocity
double rhoFace = rhoL;
double uFace = uL;
double pFace = _aAmbientPressure;
HLLCFlux(rhoFace, uFace, pFace, rhoL, uL, pL, out Fm[0], out Fp[0], out Fe[0]);
SimdInternalFluxBlock(f, vectorSize);
}
else if (_aBCType == BoundaryType.ClosedEnd)
ClosedEndFlux(rhoL, uL, pL, false, out Fm[0], out Fp[0], out Fe[0]);
else
{ Fm[0] = 0; Fp[0] = pL; Fe[0] = 0; }
// Internal faces
for (int i = 0; i < n - 1; i++)
// Scalar remainder for faces f .. n-1
for (int f = Math.Max(1, lastSimdFace + 1); f < n; f++)
{
double rhoLi = _rho[i];
double uLi = _rhou[i] / Math.Max(rhoLi, 1e-12);
double pLi = Pressure(i);
double rhoRi = _rho[i + 1];
double uRi = _rhou[i + 1] / Math.Max(rhoRi, 1e-12);
double pRi = Pressure(i + 1);
HLLCFlux(rhoLi, uLi, pLi, rhoRi, uRi, pRi, out Fm[i + 1], out Fp[i + 1], out Fe[i + 1]);
float rhoLi = _rho[f - 1];
float uLi = _rhou[f - 1] / Math.Max(rhoLi, 1e-12f);
float pLi = PressureScalar(f - 1);
float rhoRi = _rho[f];
float uRi = _rhou[f] / Math.Max(rhoRi, 1e-12f);
float pRi = PressureScalar(f);
HLLCFluxScalar(rhoLi, uLi, pLi, rhoRi, uRi, pRi,
out _fluxM[f], out _fluxP[f], out _fluxE[f]);
}
// Right boundary (face n)
double rhoR = _rho[n - 1];
double uR = _rhou[n - 1] / Math.Max(rhoR, 1e-12);
double pR = Pressure(n - 1);
if (_bBCType == BoundaryType.GhostCell && _ghostRSet)
HLLCFlux(rhoR, uR, pR, _rhoGhostR, _uGhostR, _pGhostR, out Fm[n], out Fp[n], out Fe[n]);
else if (_bBCType == BoundaryType.OpenEnd)
OpenEndFluxRight(rhoR, uR, pR, _bAmbientPressure, out Fm[n], out Fp[n], out Fe[n]);
else if (_bBCType == BoundaryType.ZeroPressureOpen)
{
double rhoFace = rhoR;
double uFace = uR;
double pFace = _bAmbientPressure;
HLLCFlux(rhoR, uR, pR, rhoFace, uFace, pFace, out Fm[n], out Fp[n], out Fe[n]);
}
else if (_bBCType == BoundaryType.ClosedEnd)
ClosedEndFlux(rhoR, uR, pR, true, out Fm[n], out Fp[n], out Fe[n]);
else
{ Fm[n] = 0; Fp[n] = pR; Fe[n] = 0; }
// --- 3. Right boundary face (index n) scalar --------------------
float rhoR = _rho[n - 1];
float uR = _rhou[n - 1] / Math.Max(rhoR, 1e-12f);
float pR = PressureScalar(n - 1);
ComputeRightBoundaryFlux(rhoR, uR, pR, out _fluxM[n], out _fluxP[n], out _fluxE[n]);
// Cell update (linear damping)
double radius = _diameter / 2.0;
double mu_air = 1.8e-5;
double laminarCoeff = DampingMultiplier * 8.0 * mu_air / (radius * radius);
for (int i = 0; i < n; i++)
{
_rho[i] -= dtSub * (Fm[i + 1] - Fm[i]) / _dx;
_rhou[i] -= dtSub * (Fp[i + 1] - Fp[i]) / _dx;
_E[i] -= dtSub * (Fe[i + 1] - Fe[i]) / _dx;
double rho = Math.Max(_rho[i], 1e-12);
double dampingFactor = Math.Exp(-(laminarCoeff / rho) * dtSub);
_rhou[i] *= dampingFactor;
if (_rho[i] < 1e-12) _rho[i] = 1e-12;
double kinetic = 0.5 * _rhou[i] * _rhou[i] / _rho[i];
double pMin = 100.0;
double eMin = pMin / ((_gamma - 1) * _rho[i]) + kinetic;
if (_E[i] < eMin) _E[i] = eMin;
}
// --- 4. Cell update + damping SIMD ------------------------------
SimdCellUpdate(dt);
}
// ---------- HLLC Riemann solver ----------
private void HLLCFlux(double rL, double uL, double pL, double rR, double uR, double pR,
out double fm, out double fp, out double fe)
// ==================== PRIVATE SCALAR HELPERS ===========================
private float PressureScalar(int i)
{
double cL = Math.Sqrt(_gamma * pL / Math.Max(rL, 1e-12));
double cR = Math.Sqrt(_gamma * pR / Math.Max(rR, 1e-12));
double EL = pL / ((_gamma - 1) * rL) + 0.5 * uL * uL;
double ER = pR / ((_gamma - 1) * rR) + 0.5 * uR * uR;
double SL = Math.Min(uL - cL, uR - cR);
double SR = Math.Max(uL + cL, uR + cR);
double Ss = (pR - pL + rL * uL * (SL - uL) - rR * uR * (SR - uR))
/ (rL * (SL - uL) - rR * (SR - uR));
float rho = Math.Max(_rho[i], 1e-12f);
return (_gamma - 1f) * (_E[i] - 0.5f * _rhou[i] * _rhou[i] / rho);
}
double FrL_m = rL * uL, FrL_p = rL * uL * uL + pL, FrL_e = (rL * EL + pL) * uL;
double FrR_m = rR * uR, FrR_p = rR * uR * uR + pR, FrR_e = (rR * ER + pR) * uR;
private void ComputeLeftBoundaryFlux(float rhoInt, float uInt, float pInt,
out float fm, out float fp, out float fe)
{
if (_aBCType == BoundaryType.GhostCell && _ghostLSet)
HLLCFluxScalar(_rhoGhostL, _uGhostL, _pGhostL, rhoInt, uInt, pInt, out fm, out fp, out fe);
else if (_aBCType == BoundaryType.OpenEnd)
OpenEndFluxLeft(rhoInt, uInt, pInt, _aAmbientPressure, out fm, out fp, out fe);
else if (_aBCType == BoundaryType.ZeroPressureOpen)
{
float rhoFace = rhoInt;
float uFace = uInt;
float pFace = _aAmbientPressure;
HLLCFluxScalar(rhoFace, uFace, pFace, rhoInt, uInt, pInt, out fm, out fp, out fe);
}
else if (_aBCType == BoundaryType.ClosedEnd)
ClosedEndFlux(rhoInt, uInt, pInt, false, out fm, out fp, out fe);
else
{ fm = 0; fp = pInt; fe = 0; }
}
private void ComputeRightBoundaryFlux(float rhoInt, float uInt, float pInt,
out float fm, out float fp, out float fe)
{
if (_bBCType == BoundaryType.GhostCell && _ghostRSet)
HLLCFluxScalar(rhoInt, uInt, pInt, _rhoGhostR, _uGhostR, _pGhostR, out fm, out fp, out fe);
else if (_bBCType == BoundaryType.OpenEnd)
OpenEndFluxRight(rhoInt, uInt, pInt, _bAmbientPressure, out fm, out fp, out fe);
else if (_bBCType == BoundaryType.ZeroPressureOpen)
{
float rhoFace = rhoInt;
float uFace = uInt;
float pFace = _bAmbientPressure;
HLLCFluxScalar(rhoInt, uInt, pInt, rhoFace, uFace, pFace, out fm, out fp, out fe);
}
else if (_bBCType == BoundaryType.ClosedEnd)
ClosedEndFlux(rhoInt, uInt, pInt, true, out fm, out fp, out fe);
else
{ fm = 0; fp = pInt; fe = 0; }
}
// ==================== SCALAR HLLC & BOUNDARY FLUX ======================
private void HLLCFluxScalar(float rL, float uL, float pL, float rR, float uR, float pR,
out float fm, out float fp, out float fe)
{
float cL = MathF.Sqrt(_gamma * pL / Math.Max(rL, 1e-12f));
float cR = MathF.Sqrt(_gamma * pR / Math.Max(rR, 1e-12f));
float EL = pL / ((_gamma - 1f) * rL) + 0.5f * uL * uL;
float ER = pR / ((_gamma - 1f) * rR) + 0.5f * uR * uR;
float SL = Math.Min(uL - cL, uR - cR);
float SR = Math.Max(uL + cL, uR + cR);
float denom = rL * (SL - uL) - rR * (SR - uR);
float Ss = (pR - pL + rL * uL * (SL - uL) - rR * uR * (SR - uR)) / denom;
float FrL_m = rL * uL, FrL_p = rL * uL * uL + pL, FrL_e = (rL * EL + pL) * uL;
float FrR_m = rR * uR, FrR_p = rR * uR * uR + pR, FrR_e = (rR * ER + pR) * uR;
if (SL >= 0) { fm = FrL_m; fp = FrL_p; fe = FrL_e; }
else if (SR <= 0) { fm = FrR_m; fp = FrR_p; fe = FrR_e; }
else if (Ss >= 0)
{
double rsL = rL * (SL - uL) / (SL - Ss);
double ps = pL + rL * (SL - uL) * (Ss - uL);
double EsL = EL + (Ss - uL) * (Ss + pL / (rL * (SL - uL)));
float rsL = rL * (SL - uL) / (SL - Ss);
float ps = pL + rL * (SL - uL) * (Ss - uL);
float EsL = EL + (Ss - uL) * (Ss + pL / (rL * (SL - uL)));
fm = rsL * Ss; fp = rsL * Ss * Ss + ps; fe = (rsL * EsL + ps) * Ss;
}
else
{
double rsR = rR * (SR - uR) / (SR - Ss);
double ps = pR + rR * (SR - uR) * (Ss - uR);
double EsR = ER + (Ss - uR) * (Ss + pR / (rR * (SR - uR)));
float rsR = rR * (SR - uR) / (SR - Ss);
float ps = pR + rR * (SR - uR) * (Ss - uR);
float EsR = ER + (Ss - uR) * (Ss + pR / (rR * (SR - uR)));
fm = rsR * Ss; fp = rsR * Ss * Ss + ps; fe = (rsR * EsR + ps) * Ss;
}
}
// ---------- Characteristic openend boundaries ----------
private void OpenEndFluxLeft(double rhoInt, double uInt, double pInt, double pAmb,
out double fm, out double fp, out double fe)
private void OpenEndFluxLeft(float rhoInt, float uInt, float pInt, float pAmb,
out float fm, out float fp, out float fe)
{
double cInt = Math.Sqrt(_gamma * pInt / Math.Max(rhoInt, 1e-12));
if (uInt <= -cInt) // supersonic inflow
float cInt = MathF.Sqrt(_gamma * pInt / Math.Max(rhoInt, 1e-12f));
if (uInt <= -cInt) // supersonic inflow
{
fm = rhoInt * uInt;
fp = rhoInt * uInt * uInt + pInt;
fe = (rhoInt * (pInt / ((_gamma - 1) * rhoInt) + 0.5 * uInt * uInt) + pInt) * uInt;
fe = (rhoInt * (pInt / ((_gamma - 1f) * rhoInt) + 0.5f * uInt * uInt) + pInt) * uInt;
return;
}
if (uInt <= 0) // subsonic inflow
if (uInt <= 0) // subsonic inflow
{
double T0 = 300.0, R = 287.0;
double ghost_Rho = pAmb / (R * T0);
HLLCFlux(ghost_Rho, 0.0, pAmb, rhoInt, uInt, pInt, out fm, out fp, out fe);
float T0 = 300f, R = 287f;
float ghostRho = pAmb / (R * T0);
HLLCFluxScalar(ghostRho, 0f, pAmb, rhoInt, uInt, pInt, out fm, out fp, out fe);
return;
}
// subsonic outflow
double s = pInt / Math.Pow(rhoInt, _gamma);
double ghostRho = Math.Pow(pAmb / s, 1.0 / _gamma);
double cGhost = Math.Sqrt(_gamma * pAmb / ghostRho);
double J_minus = uInt - 2.0 * cInt / (_gamma - 1.0);
double uGhost = J_minus + 2.0 * cGhost / (_gamma - 1.0);
float s = pInt / MathF.Pow(rhoInt, _gamma);
float ghostRho2 = MathF.Pow(pAmb / s, 1f / _gamma);
float cGhost = MathF.Sqrt(_gamma * pAmb / ghostRho2);
float J_minus = uInt - 2f * cInt / (_gamma - 1f);
float uGhost = J_minus + 2f * cGhost / (_gamma - 1f);
if (uGhost < 0) uGhost = 0;
HLLCFlux(ghostRho, uGhost, pAmb, rhoInt, uInt, pInt, out fm, out fp, out fe);
HLLCFluxScalar(ghostRho2, uGhost, pAmb, rhoInt, uInt, pInt, out fm, out fp, out fe);
}
private void OpenEndFluxRight(double rhoInt, double uInt, double pInt, double pAmb,
out double fm, out double fp, out double fe)
private void OpenEndFluxRight(float rhoInt, float uInt, float pInt, float pAmb,
out float fm, out float fp, out float fe)
{
double cInt = Math.Sqrt(_gamma * pInt / Math.Max(rhoInt, 1e-12));
if (uInt >= cInt) // supersonic outflow
float cInt = MathF.Sqrt(_gamma * pInt / Math.Max(rhoInt, 1e-12f));
if (uInt >= cInt) // supersonic outflow
{
fm = rhoInt * uInt;
fp = rhoInt * uInt * uInt + pInt;
fe = (rhoInt * (pInt / ((_gamma - 1) * rhoInt) + 0.5 * uInt * uInt) + pInt) * uInt;
fe = (rhoInt * (pInt / ((_gamma - 1f) * rhoInt) + 0.5f * uInt * uInt) + pInt) * uInt;
return;
}
if (uInt >= 0) // subsonic outflow
if (uInt >= 0) // subsonic outflow
{
double s = pInt / Math.Pow(rhoInt, _gamma);
double ghost_Rho = Math.Pow(pAmb / s, 1.0 / _gamma);
double cGhost = Math.Sqrt(_gamma * pAmb / ghost_Rho);
double J_plus = uInt + 2.0 * cInt / (_gamma - 1.0);
double uGhost = J_plus - 2.0 * cGhost / (_gamma - 1.0);
float s = pInt / MathF.Pow(rhoInt, _gamma);
float ghostRho = MathF.Pow(pAmb / s, 1f / _gamma);
float cGhost = MathF.Sqrt(_gamma * pAmb / ghostRho);
float J_plus = uInt + 2f * cInt / (_gamma - 1f);
float uGhost = J_plus - 2f * cGhost / (_gamma - 1f);
if (uGhost > 0) uGhost = 0;
HLLCFlux(rhoInt, uInt, pInt, ghost_Rho, uGhost, pAmb, out fm, out fp, out fe);
HLLCFluxScalar(rhoInt, uInt, pInt, ghostRho, uGhost, pAmb, out fm, out fp, out fe);
return;
}
// subsonic inflow
double T0 = 300.0, R = 287.0;
double ghostRho = pAmb / (R * T0);
HLLCFlux(rhoInt, uInt, pInt, ghostRho, 0.0, pAmb, out fm, out fp, out fe);
float T0 = 300f, R = 287f;
float ghostRho2 = pAmb / (R * T0);
HLLCFluxScalar(rhoInt, uInt, pInt, ghostRho2, 0f, pAmb, out fm, out fp, out fe);
}
private void ClosedEndFlux(double rhoInt, double uInt, double pInt, bool isRight,
out double fm, out double fp, out double fe)
private void ClosedEndFlux(float rhoInt, float uInt, float pInt, bool isRight,
out float fm, out float fp, out float fe)
{
double rhoGhost = rhoInt, pGhost = pInt, uGhost = -uInt;
float rhoGhost = rhoInt, pGhost = pInt, uGhost = -uInt;
if (isRight)
HLLCFlux(rhoInt, uInt, pInt, rhoGhost, uGhost, pGhost, out fm, out fp, out fe);
HLLCFluxScalar(rhoInt, uInt, pInt, rhoGhost, uGhost, pGhost, out fm, out fp, out fe);
else
HLLCFlux(rhoGhost, uGhost, pGhost, rhoInt, uInt, pInt, out fm, out fp, out fe);
HLLCFluxScalar(rhoGhost, uGhost, pGhost, rhoInt, uInt, pInt, out fm, out fp, out fe);
}
// ==================== SIMD INTERNAL FACE ROUTINE ========================
private void SimdInternalFluxBlock(int startFace, int count)
{
// startFace is the first face index; we process 'count' consecutive faces.
// For face f, left cell = f-1, right cell = f.
// We load left and right states for faces [startFace .. startFace+count-1].
int leftIdx = startFace - 1;
int rightIdx = startFace;
// Load conserved variables for left cells and right cells as vectors.
Vector<float> rL = new Vector<float>(_rho, leftIdx);
Vector<float> ruL = new Vector<float>(_rhou, leftIdx);
Vector<float> EL = new Vector<float>(_E, leftIdx);
Vector<float> rR = new Vector<float>(_rho, rightIdx);
Vector<float> ruR = new Vector<float>(_rhou, rightIdx);
Vector<float> ER = new Vector<float>(_E, rightIdx);
// Derived quantities: u = ru / r, p = (gamma-1)*(E - 0.5*ru^2 / r)
Vector<float> uL = ruL / rL;
Vector<float> uR = ruR / rR;
Vector<float> half = new Vector<float>(0.5f);
Vector<float> gammaMinus1 = new Vector<float>(_gamma - 1f);
Vector<float> gammaVec = new Vector<float>(_gamma);
Vector<float> pL = gammaMinus1 * (EL - half * ruL * ruL / rL);
Vector<float> pR = gammaMinus1 * (ER - half * ruR * ruR / rR);
// Sound speeds
Vector<float> cL = Vector.SquareRoot(gammaVec * pL / rL);
Vector<float> cR = Vector.SquareRoot(gammaVec * pR / rR);
// Wave speeds
Vector<float> SL = Vector.Min(uL - cL, uR - cR);
Vector<float> SR = Vector.Max(uL + cL, uR + cR);
// Star speed
Vector<float> num = (pR - pL) + rL * uL * (SL - uL) - rR * uR * (SR - uR);
Vector<float> den = rL * (SL - uL) - rR * (SR - uR);
Vector<float> Ss = num / den;
// Total energy per unit mass (E/rho) for left/right (needed for star region)
Vector<float> eL = EL / rL;
Vector<float> eR = ER / rR;
// --- Compute all four possible flux vectors ---
// Left flux
Vector<float> Fm_L = ruL;
Vector<float> Fp_L = ruL * uL + pL;
Vector<float> Fe_L = (EL + pL) * uL; // (r*E + p)*u
// Right flux
Vector<float> Fm_R = ruR;
Vector<float> Fp_R = ruR * uR + pR;
Vector<float> Fe_R = (ER + pR) * uR;
// Starleft fluxes (when SL < 0 < Ss)
Vector<float> diffL = SL - uL;
Vector<float> dL_den = SL - Ss;
Vector<float> rsL = rL * diffL / dL_den;
Vector<float> psSL = pL + rL * diffL * (Ss - uL);
Vector<float> EsL = eL + (Ss - uL) * (Ss + pL / (rL * diffL));
Vector<float> Fm_starL = rsL * Ss;
Vector<float> Fp_starL = rsL * Ss * Ss + psSL;
Vector<float> Fe_starL = (rsL * EsL + psSL) * Ss;
// Starright fluxes (when Ss < 0 < SR)
Vector<float> diffR = SR - uR;
Vector<float> dR_den = SR - Ss;
Vector<float> rsR = rR * diffR / dR_den;
Vector<float> psSR = pR + rR * diffR * (Ss - uR);
Vector<float> EsR = eR + (Ss - uR) * (Ss + pR / (rR * diffR));
Vector<float> Fm_starR = rsR * Ss;
Vector<float> Fp_starR = rsR * Ss * Ss + psSR;
Vector<float> Fe_starR = (rsR * EsR + psSR) * Ss;
// --- Select the correct flux based on wave signs ---
var maskSLge0 = Vector.GreaterThanOrEqual(SL, Vector<float>.Zero);
var maskSRle0 = Vector.LessThanOrEqual(SR, Vector<float>.Zero);
var maskMiddle = ~(maskSLge0 | maskSRle0); // SL<0 && SR>0
var maskStarL = maskMiddle & Vector.GreaterThanOrEqual(Ss, Vector<float>.Zero);
var maskStarR = maskMiddle & Vector.LessThan(Ss, Vector<float>.Zero);
// Start with left flux, override with right/star as needed
Vector<float> fm = Vector.ConditionalSelect(maskSLge0, Fm_L,
Vector.ConditionalSelect(maskSRle0, Fm_R,
Vector.ConditionalSelect(maskStarL, Fm_starL,
Vector.ConditionalSelect(maskStarR, Fm_starR, Vector<float>.Zero))));
Vector<float> fp = Vector.ConditionalSelect(maskSLge0, Fp_L,
Vector.ConditionalSelect(maskSRle0, Fp_R,
Vector.ConditionalSelect(maskStarL, Fp_starL,
Vector.ConditionalSelect(maskStarR, Fp_starR, Vector<float>.Zero))));
Vector<float> fe = Vector.ConditionalSelect(maskSLge0, Fe_L,
Vector.ConditionalSelect(maskSRle0, Fe_R,
Vector.ConditionalSelect(maskStarL, Fe_starL,
Vector.ConditionalSelect(maskStarR, Fe_starR, Vector<float>.Zero))));
// Store to flux arrays at indices startFace .. startFace+count-1
fm.CopyTo(_fluxM, startFace);
fp.CopyTo(_fluxP, startFace);
fe.CopyTo(_fluxE, startFace);
}
// ==================== SIMD CELL UPDATE + DAMPING ========================
private void SimdCellUpdate(float dt)
{
float dt_dx = dt / _dx;
Vector<float> vDtDx = new Vector<float>(dt_dx);
float coeff = _laminarCoeff * (float)DampingMultiplier;
Vector<float> vCoeff = new Vector<float>(coeff);
Vector<float> vDt = new Vector<float>(dt);
int vectorSize = Vector<float>.Count;
int n = _n;
int lastSimdCell = n - vectorSize;
// Predefine constants used in clamping
Vector<float> half = new Vector<float>(0.5f);
Vector<float> gammaMinus1 = new Vector<float>(_gamma - 1f);
for (int i = 0; i <= lastSimdCell; i += vectorSize)
{
// Load conserved
Vector<float> r = new Vector<float>(_rho, i);
Vector<float> ru = new Vector<float>(_rhou, i);
Vector<float> E = new Vector<float>(_E, i);
// Load fluxes at faces i (left) and i+1 (right)
Vector<float> flxM_L = new Vector<float>(_fluxM, i);
Vector<float> flxM_R = new Vector<float>(_fluxM, i + 1);
Vector<float> flxP_L = new Vector<float>(_fluxP, i);
Vector<float> flxP_R = new Vector<float>(_fluxP, i + 1);
Vector<float> flxE_L = new Vector<float>(_fluxE, i);
Vector<float> flxE_R = new Vector<float>(_fluxE, i + 1);
// Update conserved: Q_new = Q - dt/dx * (flux_right - flux_left)
Vector<float> newR = r - vDtDx * (flxM_R - flxM_L);
Vector<float> newRu = ru - vDtDx * (flxP_R - flxP_L);
Vector<float> newE = E - vDtDx * (flxE_R - flxE_L);
// Damping
Vector<float> factor = Vector.Exp(-vCoeff / r * vDt);
newRu *= factor;
// Clamp density
newR = Vector.Max(newR, new Vector<float>(1e-12f));
// Enforce minimal pressure (100 Pa) -> minimal energy
Vector<float> kinE = half * newRu * newRu / newR;
Vector<float> eMin = new Vector<float>(100f) / gammaMinus1 + kinE;
newE = Vector.Max(newE, eMin);
newR.CopyTo(_rho, i);
newRu.CopyTo(_rhou, i);
newE.CopyTo(_E, i);
}
// Scalar remainder
for (int i = Math.Max(0, lastSimdCell + 1); i < n; i++)
{
float r = _rho[i];
float ru = _rhou[i];
float E = _E[i];
float dM = _fluxM[i + 1] - _fluxM[i];
float dP = _fluxP[i + 1] - _fluxP[i];
float dE = _fluxE[i + 1] - _fluxE[i];
float newR = r - dt_dx * dM;
float newRu = ru - dt_dx * dP;
float newE = E - dt_dx * dE;
// Damping
float factor = MathF.Exp(-coeff / Math.Max(r, 1e-12f) * dt);
newRu *= factor;
// Clamps
if (newR < 1e-12f) newR = 1e-12f;
float kin = 0.5f * newRu * newRu / newR;
float emin = 100f / (_gamma - 1f) + kin;
if (newE < emin) newE = emin;
_rho[i] = newR;
_rhou[i] = newRu;
_E[i] = newE;
}
}
}
}