Files
FluidSim/Components/Pipe1D.cs
2026-05-05 11:24:32 +02:00

609 lines
25 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 System.Numerics;
using FluidSim.Interfaces;
namespace FluidSim.Components
{
public enum BoundaryType
{
OpenEnd,
ZeroPressureOpen,
ClosedEnd,
GhostCell
}
public class Pipe1D
{
public Port PortA { get; }
public Port PortB { get; }
public double Area => _area;
public double DampingMultiplier { get; set; } = 1.0;
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 float _rhoGhostL, _uGhostL, _pGhostL;
private float _rhoGhostR, _uGhostR, _pGhostR;
private bool _ghostLSet, _ghostRSet;
private BoundaryType _aBCType = BoundaryType.GhostCell;
private BoundaryType _bBCType = BoundaryType.GhostCell;
private float _aAmbientPressure = 101325f;
private float _bAmbientPressure = 101325f;
// 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)
{
float dtGlobal = 1f / sampleRate;
int nCells;
float dxTarget = ReferenceSoundSpeed * dtGlobal / CflTarget;
if (forcedCellCount > 1)
nCells = forcedCellCount;
else
{
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 = (float)(length / nCells);
_dt = dtGlobal;
_area = (float)area;
_diameter = (float)(2.0 * Math.Sqrt(area / Math.PI));
_gamma = 1.4f;
_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 = (float)p;
public void SetBAmbientPressure(double p) => _bAmbientPressure = (float)p;
public void SetGhostLeft(double rho, double u, double p)
{
_rhoGhostL = (float)rho;
_uGhostL = (float)u;
_pGhostL = (float)p;
_ghostLSet = true;
}
public void SetGhostRight(double rho, double u, double p)
{
_rhoGhostR = (float)rho;
_uGhostR = (float)u;
_pGhostR = (float)p;
_ghostRSet = true;
}
public void ClearGhostFlag()
{
_ghostLSet = false;
_ghostRSet = false;
}
public void SetUniformState(double rho, double u, double p)
{
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] = 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;
float rho = _rho[lastCell];
float u = _rhou[lastCell] / Math.Max(rho, 1e-12f);
float p = PressureScalar(lastCell);
float c = MathF.Sqrt(_gamma * p / rho);
float uFace = u;
float rhoFace = rho;
float pFace = p;
if (uFace > 0 && uFace < c) // subsonic outflow
{
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;
pFace = _bAmbientPressure;
}
return rhoFace * uFace * _area;
}
public double GetAndStoreMassFlowForDerivative()
{
float current = (float)GetOpenEndMassFlow();
double derivative = (current - _lastMassFlow) / _dt;
_lastMassFlow = current;
return derivative;
}
public int GetRequiredSubSteps(double dtGlobal, double cflTarget = 0.8f)
{
float maxW = 0f;
for (int i = 0; i < _n; i++)
{
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-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;
// --- 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)
{
SimdInternalFluxBlock(f, vectorSize);
}
// Scalar remainder for faces f .. n-1
for (int f = Math.Max(1, lastSimdFace + 1); f < n; f++)
{
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]);
}
// --- 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]);
// --- 4. Cell update + damping SIMD ------------------------------
SimdCellUpdate(dt);
}
// ==================== PRIVATE SCALAR HELPERS ===========================
private float PressureScalar(int i)
{
float rho = Math.Max(_rho[i], 1e-12f);
return (_gamma - 1f) * (_E[i] - 0.5f * _rhou[i] * _rhou[i] / rho);
}
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)
{
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
{
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;
}
}
private void OpenEndFluxLeft(float rhoInt, float uInt, float pInt, float pAmb,
out float fm, out float fp, out float fe)
{
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 - 1f) * rhoInt) + 0.5f * uInt * uInt) + pInt) * uInt;
return;
}
if (uInt <= 0) // subsonic inflow
{
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
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;
HLLCFluxScalar(ghostRho2, uGhost, pAmb, rhoInt, uInt, pInt, out fm, out fp, out fe);
}
private void OpenEndFluxRight(float rhoInt, float uInt, float pInt, float pAmb,
out float fm, out float fp, out float fe)
{
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 - 1f) * rhoInt) + 0.5f * uInt * uInt) + pInt) * uInt;
return;
}
if (uInt >= 0) // subsonic outflow
{
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;
HLLCFluxScalar(rhoInt, uInt, pInt, ghostRho, uGhost, pAmb, out fm, out fp, out fe);
return;
}
// subsonic inflow
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(float rhoInt, float uInt, float pInt, bool isRight,
out float fm, out float fp, out float fe)
{
float rhoGhost = rhoInt, pGhost = pInt, uGhost = -uInt;
if (isRight)
HLLCFluxScalar(rhoInt, uInt, pInt, rhoGhost, uGhost, pGhost, out fm, out fp, out fe);
else
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;
}
}
}
}