Helmholtz testing (no decay bug)
This commit is contained in:
163
Core/Solver.cs
163
Core/Solver.cs
@@ -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 sub‑stepping (0.3‑0.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 sub‑steps
|
||||
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($" Sub‑step 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user