engine almost working, backup before adding gas types.

This commit is contained in:
max
2026-05-07 20:07:15 +02:00
parent 14f5ba925f
commit 92d84eacfe
18 changed files with 1236 additions and 587 deletions

View File

@@ -1,4 +1,5 @@
using System;
using System.Diagnostics;
using FluidSim.Interfaces;
namespace FluidSim.Components
@@ -9,6 +10,9 @@ namespace FluidSim.Components
/// </summary>
public class Pipe1D : IComponent
{
// ---------- Compiletime profiling flag ----------
public const bool EnableDetailedProfiling = false; // set to false in release builds
public Port PortA { get; }
public Port PortB { get; }
public double Area { get; }
@@ -32,7 +36,7 @@ namespace FluidSim.Components
private readonly double _gamma = 1.4;
private double[] _rho, _rhou, _E;
private double[] _fluxM, _fluxP, _fluxE; // flux at cell faces (0.._n)
private double[] _fluxM, _fluxP, _fluxE; // flux at cell faces (0.._n) kept for possible external use, not used internally anymore
private double _rhoGhostL, _uGhostL, _pGhostL;
private double _rhoGhostR, _uGhostR, _pGhostR;
@@ -41,6 +45,14 @@ namespace FluidSim.Components
private double _laminarCoeff;
private double _ambientEnergyReference;
// ---------- Profiling accumulators ----------
private long _profPrecomputeTicks;
private long _profLeftFluxTicks;
private long _profInteriorLoopTicks;
private long _profRightFluxTicks;
private long _profPortUpdateTicks;
private long _profCallCount;
public Pipe1D(double length, double area, int cellCount)
{
if (cellCount < 4) throw new ArgumentException("cellCount must be at least 4");
@@ -128,84 +140,142 @@ namespace FluidSim.Components
double dt = dtSub;
int n = _n;
// ---- Compute fluxes at all faces using LaxFriedrichs ----
// Left face (0): between ghostL and cell 0
double rL = Math.Max(_rhoGhostL, 1e-12);
double pL = _pGhostL;
double uL = _uGhostL;
double eL = pL / ((_gamma - 1.0) * rL) + 0.5 * uL * uL;
double rR = Math.Max(_rho[0], 1e-12);
double pR = PressureScalar(0);
double uR = _rhou[0] / rR;
double eR = pR / ((_gamma - 1.0) * rR) + 0.5 * uR * uR;
LaxFriedrichsFlux(rL, uL, pL, eL, rR, uR, pR, eR,
out _fluxM[0], out _fluxP[0], out _fluxE[0]);
// Internal faces (1 .. n-1)
for (int f = 1; f < n; f++)
{
int iL = f - 1;
int iR = f;
rL = Math.Max(_rho[iL], 1e-12);
pL = PressureScalar(iL);
uL = _rhou[iL] / rL;
eL = pL / ((_gamma - 1.0) * rL) + 0.5 * uL * uL;
rR = Math.Max(_rho[iR], 1e-12);
pR = PressureScalar(iR);
uR = _rhou[iR] / rR;
eR = pR / ((_gamma - 1.0) * rR) + 0.5 * uR * uR;
LaxFriedrichsFlux(rL, uL, pL, eL, rR, uR, pR, eR,
out _fluxM[f], out _fluxP[f], out _fluxE[f]);
}
// Right face (n): between cell n-1 and ghostR
rL = Math.Max(_rho[n - 1], 1e-12);
pL = PressureScalar(n - 1);
uL = _rhou[n - 1] / rL;
eL = pL / ((_gamma - 1.0) * rL) + 0.5 * uL * uL;
rR = Math.Max(_rhoGhostR, 1e-12);
pR = _pGhostR;
uR = _uGhostR;
eR = pR / ((_gamma - 1.0) * rR) + 0.5 * uR * uR;
LaxFriedrichsFlux(rL, uL, pL, eL, rR, uR, pR, eR,
out _fluxM[n], out _fluxP[n], out _fluxE[n]);
// ---- Cell update ----
double dt_dx = dt / _dx;
double coeff = _laminarCoeff * DampingMultiplier;
double relaxRate = EnergyRelaxationRate;
double gamma = _gamma;
double gm1 = gamma - 1.0;
// ---------- Profiling start ----------
long t0 = 0, t1 = 0;
if (EnableDetailedProfiling)
{
t0 = Stopwatch.GetTimestamp();
_profCallCount++;
}
// ---------- Phase 1: Precompute pressure and speed of sound ----------
double[] p = new double[n];
double[] c = new double[n];
for (int i = 0; i < n; i++)
{
double rho = Math.Max(_rho[i], 1e-12);
double u = _rhou[i] / rho;
p[i] = gm1 * (_E[i] - 0.5 * _rhou[i] * _rhou[i] / rho);
c[i] = Math.Sqrt(gamma * p[i] / rho);
}
if (EnableDetailedProfiling)
{
t1 = Stopwatch.GetTimestamp();
_profPrecomputeTicks += (t1 - t0);
t0 = t1;
}
// ---------- Phase 2: Left face flux (ghostL cell 0) ----------
double rL_ghost = Math.Max(_rhoGhostL, 1e-12);
double pL_ghost = _pGhostL;
double uL_ghost = _uGhostL;
double cL_ghost = Math.Sqrt(gamma * pL_ghost / rL_ghost);
LaxFlux(rL_ghost, uL_ghost, pL_ghost, cL_ghost,
_rho[0], _rhou[0] / Math.Max(_rho[0], 1e-12), p[0], c[0],
out double fluxM_left, out double fluxP_left, out double fluxE_left);
if (EnableDetailedProfiling)
{
t1 = Stopwatch.GetTimestamp();
_profLeftFluxTicks += (t1 - t0);
t0 = t1;
}
// ---------- Phase 3: Interior loop (fluxes + cell updates) ----------
double fluxM_prev = fluxM_left;
double fluxP_prev = fluxP_left;
double fluxE_prev = fluxE_left;
for (int i = 0; i < n - 1; i++)
{
int iL = i;
int iR = i + 1;
double rL = Math.Max(_rho[iL], 1e-12);
double uL = _rhou[iL] / rL;
double pL = p[iL];
double cL = c[iL];
double rR = Math.Max(_rho[iR], 1e-12);
double uR = _rhou[iR] / rR;
double pR = p[iR];
double cR = c[iR];
LaxFlux(rL, uL, pL, cL, rR, uR, pR, cR,
out double fluxM_right, out double fluxP_right, out double fluxE_right);
// Update cell i
double r = _rho[i];
double ru = _rhou[i];
double E = _E[i];
double dM = _fluxM[i + 1] - _fluxM[i];
double dP = _fluxP[i + 1] - _fluxP[i];
double dE_flux = _fluxE[i + 1] - _fluxE[i];
double newR = r - dt_dx * dM;
double newRu = ru - dt_dx * dP;
double newE = E - dt_dx * dE_flux;
double newR = r - dt_dx * (fluxM_right - fluxM_prev);
double newRu = ru - dt_dx * (fluxP_right - fluxP_prev);
double newE = E - dt_dx * (fluxE_right - fluxE_prev);
double dampingFactor = Math.Exp(-coeff / Math.Max(r, 1e-12) * dt);
newRu *= dampingFactor;
double relaxFactor = Math.Exp(-relaxRate * dt);
newE = _ambientEnergyReference + (newE - _ambientEnergyReference) * relaxFactor;
newR = Math.Max(newR, 1e-12);
double kin = 0.5 * newRu * newRu / Math.Max(newR, 1e-12);
double eMin = 100.0 / (_gamma - 1.0) + kin;
double eMin = 100.0 / gm1 + kin;
newE = Math.Max(newE, eMin);
_rho[i] = newR;
_rhou[i] = newRu;
_E[i] = newE;
fluxM_prev = fluxM_right;
fluxP_prev = fluxP_right;
fluxE_prev = fluxE_right;
}
if (EnableDetailedProfiling)
{
t1 = Stopwatch.GetTimestamp();
_profInteriorLoopTicks += (t1 - t0);
t0 = t1;
}
// ---------- Phase 4: Right face flux (cell n1 ghostR) ----------
double rR_ghost = Math.Max(_rhoGhostR, 1e-12);
double pR_ghost = _pGhostR;
double uR_ghost = _uGhostR;
double cR_ghost = Math.Sqrt(gamma * pR_ghost / rR_ghost);
LaxFlux(_rho[n - 1], _rhou[n - 1] / Math.Max(_rho[n - 1], 1e-12), p[n - 1], c[n - 1],
rR_ghost, uR_ghost, pR_ghost, cR_ghost,
out double fluxM_right_final, out double fluxP_right_final, out double fluxE_right_final);
// Update last cell (identical to interior, but with final fluxes)
{
int i = n - 1;
double r = _rho[i];
double ru = _rhou[i];
double E = _E[i];
double newR = r - dt_dx * (fluxM_right_final - fluxM_prev);
double newRu = ru - dt_dx * (fluxP_right_final - fluxP_prev);
double newE = E - dt_dx * (fluxE_right_final - fluxE_prev);
double dampingFactor = Math.Exp(-coeff / Math.Max(r, 1e-12) * dt);
newRu *= dampingFactor;
double relaxFactor = Math.Exp(-relaxRate * dt);
newE = _ambientEnergyReference + (newE - _ambientEnergyReference) * relaxFactor;
newR = Math.Max(newR, 1e-12);
double kin = 0.5 * newRu * newRu / Math.Max(newR, 1e-12);
double eMin = 100.0 / gm1 + kin;
newE = Math.Max(newE, eMin);
_rho[i] = newR;
@@ -213,43 +283,68 @@ namespace FluidSim.Components
_E[i] = newE;
}
// Update port states
if (EnableDetailedProfiling)
{
t1 = Stopwatch.GetTimestamp();
_profRightFluxTicks += (t1 - t0);
t0 = t1;
}
// ---------- Phase 5: Update port states ----------
(double rhoA, double uA, double pA) = GetInteriorStateLeft();
PortA.Pressure = pA; PortA.Density = rhoA;
PortA.Temperature = pA / (rhoA * 287.0);
PortA.SpecificEnthalpy = _gamma / (_gamma - 1.0) * pA / rhoA;
PortA.SpecificEnthalpy = gm1 / (gamma - 1.0) * pA / rhoA;
(double rhoB, double uB, double pB) = GetInteriorStateRight();
PortB.Pressure = pB; PortB.Density = rhoB;
PortB.Temperature = pB / (rhoB * 287.0);
PortB.SpecificEnthalpy = _gamma / (_gamma - 1.0) * pB / rhoB;
PortB.SpecificEnthalpy = gm1 / (gamma - 1.0) * pB / rhoB;
if (EnableDetailedProfiling)
{
t1 = Stopwatch.GetTimestamp();
_profPortUpdateTicks += (t1 - t0);
}
}
// ---------- LaxFriedrichs flux ----------
// ---------- Local LaxFriedrichs flux function ----------
private void LaxFlux(double rL, double uL, double pL, double cL,
double rR, double uR, double pR, double cR,
out double fm, out double fp, out double fe)
{
double gm1 = _gamma - 1.0;
double EL = pL / (gm1 * rL) + 0.5 * uL * uL;
double ER = pR / (gm1 * rR) + 0.5 * uR * uR;
double Fm_L = rL * uL;
double Fp_L = rL * uL * uL + pL;
double Fe_L = (rL * EL + pL) * uL;
double Fm_R = rR * uR;
double Fp_R = rR * uR * uR + pR;
double Fe_R = (rR * ER + pR) * uR;
double alpha = Math.Max(Math.Abs(uL) + cL, Math.Abs(uR) + cR);
fm = 0.5 * (Fm_L + Fm_R) - 0.5 * alpha * (rR - rL);
fp = 0.5 * (Fp_L + Fp_R) - 0.5 * alpha * (rR * uR - rL * uL);
fe = 0.5 * (Fe_L + Fe_R) - 0.5 * alpha * (rR * ER - rL * EL);
}
// Original LaxFriedrichsFlux (kept for compatibility, can be removed if unused)
private void LaxFriedrichsFlux(double rL, double uL, double pL, double eL,
double rR, double uR, double pR, double eR,
out double fm, out double fp, out double fe)
{
// Primitive states
double rhoL = rL, rhoR = rR;
double EL = rhoL * eL; // total energy per volume = rho * (specific total energy)
double EL = rhoL * eL;
double ER = rhoR * eR;
// Conserved vectors U = (ρ, ρu, E)
// Flux F = (ρu, ρu²+p, (E+p)u)
double Fm_L = rhoL * uL;
double Fp_L = rhoL * uL * uL + pL;
double Fe_L = (EL + pL) * uL;
double Fm_R = rhoR * uR;
double Fp_R = rhoR * uR * uR + pR;
double Fe_R = (ER + pR) * uR;
// LaxFriedrichs dissipation coefficient α = max(|u|+c) over whole domain, but here we use local max to be simple:
double cL = Math.Sqrt(_gamma * pL / rL);
double cR = Math.Sqrt(_gamma * pR / rR);
double alpha = Math.Max(Math.Abs(uL) + cL, Math.Abs(uR) + cR);
fm = 0.5 * (Fm_L + Fm_R) - 0.5 * alpha * (rhoR - rhoL);
fp = 0.5 * (Fp_L + Fp_R) - 0.5 * alpha * (rhoR * uR - rhoL * uL);
fe = 0.5 * (Fe_L + Fe_R) - 0.5 * alpha * (ER - EL);
@@ -291,5 +386,42 @@ namespace FluidSim.Components
double e = p / ((_gamma - 1.0) * rho);
_E[i] = rho * e + 0.5 * rho * u * u;
}
// ---------- Public profiling interface ----------
public void ResetDetailCounters()
{
_profPrecomputeTicks = 0;
_profLeftFluxTicks = 0;
_profInteriorLoopTicks = 0;
_profRightFluxTicks = 0;
_profPortUpdateTicks = 0;
_profCallCount = 0;
}
public string GetDetailProfileReport()
{
if (!EnableDetailedProfiling)
return "Detailed profiling disabled.";
double freq = Stopwatch.Frequency;
long totalTicks = _profPrecomputeTicks + _profLeftFluxTicks +
_profInteriorLoopTicks + _profRightFluxTicks +
_profPortUpdateTicks;
if (totalTicks == 0) return "No profiling data.";
double totalSec = totalTicks / freq;
double avgCallSec = totalSec / _profCallCount;
double avgCallUs = avgCallSec * 1e6;
string report = $" Pipe detailed (over {_profCallCount} calls, total {totalSec * 1000:F2} ms):\n";
report += $" Avg per call: {avgCallUs:F2} µs\n";
report += $" Precompute p,c: {_profPrecomputeTicks * 100.0 / totalTicks:F1} % ({_profPrecomputeTicks / freq * 1e6 / _profCallCount:F2} µs/call)\n";
report += $" Left face flux: {_profLeftFluxTicks * 100.0 / totalTicks:F1} % ({_profLeftFluxTicks / freq * 1e6 / _profCallCount:F2} µs/call)\n";
report += $" Interior loop: {_profInteriorLoopTicks * 100.0 / totalTicks:F1} % ({_profInteriorLoopTicks / freq * 1e6 / _profCallCount:F2} µs/call)\n";
report += $" Right face flux: {_profRightFluxTicks * 100.0 / totalTicks:F1} % ({_profRightFluxTicks / freq * 1e6 / _profCallCount:F2} µs/call)\n";
report += $" Port update: {_profPortUpdateTicks * 100.0 / totalTicks:F1} % ({_profPortUpdateTicks / freq * 1e6 / _profCallCount:F2} µs/call)\n";
return report;
}
}
}