Helmholtz testing (no decay bug)

This commit is contained in:
max
2026-05-09 01:44:35 +02:00
parent 9c9e23147a
commit 77ef4753a3
23 changed files with 1811 additions and 2118 deletions

View File

@@ -10,136 +10,91 @@ namespace FluidSim.Core
public class Solver
{
private readonly List<IComponent> _components = new();
private readonly List<OrificeLink> _orificeLinks = new();
private readonly List<OpenEndLink> _openEndLinks = new();
private PipeSystem _pipeSystem;
private BoundarySystem _boundarySystem;
private double _dt;
/// <summary>CFL target for substepping (0.30.8). Lower values are safer for shocks.</summary>
public double CflTarget { get; set; } = 0.9;
public int SubStepCount { get; set; } = 4;
public bool EnableProfiling { get; set; } = false;
// ---------- Timing accumulators (reset every LogInterval steps) ----------
private long _stepCount;
private double _timeTotal, _timeCFL, _timeOrifice, _timeOpenEnd,
_timePipe, _timeClearGhosts, _timeUpdateState;
private const int LogInterval = 5000;
private const bool EnableLogging = false; // temporarily ON for debugging
private long _ticksOrifice, _ticksOpenEnd, _ticksPipe, _ticksUpdate;
public void SetTimeStep(double dt) => _dt = dt;
public void AddComponent(IComponent component) => _components.Add(component);
public void AddOrificeLink(OrificeLink link) => _orificeLinks.Add(link);
public void AddOpenEndLink(OpenEndLink link) => _openEndLinks.Add(link);
public void SetPipeSystem(PipeSystem pipeSystem)
{
_pipeSystem = pipeSystem;
}
public void SetBoundarySystem(BoundarySystem boundarySystem)
{
_boundarySystem = boundarySystem;
}
public void Step()
{
var pipes = _components.OfType<Pipe1D>().ToList();
if (pipes.Count == 0) return;
if (_pipeSystem == null || _boundarySystem == null) return;
var sw = Stopwatch.StartNew();
// CFL count track which pipe demands the most substeps
int nSub = 1;
Pipe1D worstPipe = pipes[0];
foreach (var p in pipes)
{
int n = p.GetRequiredSubSteps(_dt, CflTarget);
if (n > nSub)
{
nSub = n;
worstPipe = p;
}
}
double dtSub = _dt / nSub;
// ----- Diagnostic: warn if nSub is high -----
if (nSub > 50)
{
double maxW = 0;
for (int i = 0; i < worstPipe.CellCount; i++)
{
double rho = worstPipe.GetCellDensity(i);
double u = Math.Abs(worstPipe.GetCellVelocity(i));
double p = worstPipe.GetCellPressure(i);
double c = Math.Sqrt(1.4 * p / Math.Max(rho, 1e-12));
if (u + c > maxW) maxW = u + c;
}
Console.WriteLine($"nSub = {nSub} (worst pipe: {worstPipe.Name}, maxW = {maxW:F0} m/s)");
}
_timeCFL += sw.Elapsed.TotalSeconds;
// ----- Safety cap prevent the solver from hanging -----
const int maxSubSteps = 10000;
const int hardLimit = 500; // temporary low cap for debugging
if (nSub > hardLimit)
{
Console.WriteLine($"nSub ({nSub}) exceeds hard limit {hardLimit}. Simulation step skipped.");
return;
}
int nSub = SubStepCount;
float dtSub = (float)(_dt / nSub);
for (int sub = 0; sub < nSub; sub++)
{
double t0;
long t0;
t0 = sw.Elapsed.TotalSeconds;
foreach (var link in _orificeLinks)
link.Resolve(dtSub);
_timeOrifice += sw.Elapsed.TotalSeconds - t0;
t0 = Stopwatch.GetTimestamp();
_boundarySystem.ResolveOrifices(dtSub);
_ticksOrifice += Stopwatch.GetTimestamp() - t0;
t0 = sw.Elapsed.TotalSeconds;
foreach (var link in _openEndLinks)
link.Resolve(dtSub);
_timeOpenEnd += sw.Elapsed.TotalSeconds - t0;
t0 = Stopwatch.GetTimestamp();
_boundarySystem.ResolveOpenEnds(dtSub);
_ticksOpenEnd += Stopwatch.GetTimestamp() - t0;
t0 = sw.Elapsed.TotalSeconds;
foreach (var p in pipes)
p.SimulateSingleStep(dtSub);
_timePipe += sw.Elapsed.TotalSeconds - t0;
t0 = Stopwatch.GetTimestamp();
_pipeSystem.SimulateStep(dtSub);
_ticksPipe += Stopwatch.GetTimestamp() - t0;
}
double tCG = sw.Elapsed.TotalSeconds;
foreach (var p in pipes)
p.ClearGhostFlags();
_timeClearGhosts += sw.Elapsed.TotalSeconds - tCG;
double tUS = sw.Elapsed.TotalSeconds;
long tUS = Stopwatch.GetTimestamp();
foreach (var comp in _components)
comp.UpdateState(_dt);
_timeUpdateState += sw.Elapsed.TotalSeconds - tUS;
_timeTotal += sw.Elapsed.TotalSeconds;
comp.UpdateState((float)_dt);
_ticksUpdate += Stopwatch.GetTimestamp() - tUS;
_stepCount++;
if (_stepCount % LogInterval == 0 && EnableLogging)
if (_stepCount % 5000 == 0 && EnableProfiling)
{
if (_timeTotal > 0)
{
double stepsPerSec = LogInterval / _timeTotal;
double avgUs = (_timeTotal / LogInterval) * 1e6;
double freq = Stopwatch.Frequency;
double total = _ticksOrifice + _ticksOpenEnd + _ticksPipe + _ticksUpdate;
double avgStepUs = (total / freq) * 1e6 / 5000.0;
Console.WriteLine($"--- Solver timing ({LogInterval} steps) ---");
Console.WriteLine($" Steps per second: {stepsPerSec:F1}");
Console.WriteLine($" Avg step time: {avgUs:F1} µs (last nSub = {nSub})");
Console.WriteLine($" CFL calc: {_timeCFL / _timeTotal * 100:F1} %");
Console.WriteLine($" Substep loop:");
Console.WriteLine($" Orifice: {_timeOrifice / _timeTotal * 100:F1} %");
Console.WriteLine($" OpenEnd: {_timeOpenEnd / _timeTotal * 100:F1} %");
Console.WriteLine($" Pipe steps: {_timePipe / _timeTotal * 100:F1} %");
Console.WriteLine($" Clear ghosts: {_timeClearGhosts / _timeTotal * 100:F1} %");
Console.WriteLine($" Update state: {_timeUpdateState / _timeTotal * 100:F1} %");
Console.WriteLine();
int orificeCalls = 5000 * nSub;
int updateCalls = 5000;
double orificeMs = _ticksOrifice * 1000.0 / freq;
double openEndMs = _ticksOpenEnd * 1000.0 / freq;
double pipeMs = _ticksPipe * 1000.0 / freq;
double updateMs = _ticksUpdate * 1000.0 / freq;
double orificeAvgUs = orificeMs * 1000.0 / orificeCalls;
double openEndAvgUs = openEndMs * 1000.0 / orificeCalls;
double pipeAvgUs = pipeMs * 1000.0 / orificeCalls;
double updateAvgUs = updateMs * 1000.0 / updateCalls;
Console.WriteLine($"--- Solver ({5000} steps, nSub={nSub}) ---");
Console.WriteLine($" Average step: {avgStepUs:F2} µs");
Console.WriteLine($" Orifice: {orificeMs:F2} ms ({(double)_ticksOrifice / total * 100:F1}%), avg {orificeAvgUs:F2} µs/call");
Console.WriteLine($" OpenEnd: {openEndMs:F2} ms ({(double)_ticksOpenEnd / total * 100:F1}%), avg {openEndAvgUs:F2} µs/call");
Console.WriteLine($" Pipe: {pipeMs:F2} ms ({(double)_ticksPipe / total * 100:F1}%), avg {pipeAvgUs:F2} µs/call");
Console.WriteLine($" Update: {updateMs:F2} ms ({(double)_ticksUpdate / total * 100:F1}%), avg {updateAvgUs:F2} µs/call");
// Pipe internal breakdown (with per-phase averages)
if (_pipeSystem.EnableProfiling)
{
Console.WriteLine(_pipeSystem.GetProfileReport());
}
_timeTotal = 0;
_timeCFL = 0;
_timeOrifice = 0;
_timeOpenEnd = 0;
_timePipe = 0;
_timeClearGhosts = 0;
_timeUpdateState = 0;
_ticksOrifice = _ticksOpenEnd = _ticksPipe = _ticksUpdate = 0;
}
}
}