seemingly working, added display text

This commit is contained in:
2026-05-07 16:37:12 +02:00
parent f79cf6b7eb
commit 14f5ba925f
10 changed files with 288 additions and 161 deletions

View File

@@ -51,11 +51,11 @@ namespace FluidSim.Components
/// Set the pressure to a specific value while keeping the current temperature constant.
/// Updates Mass and InternalEnergy accordingly.
/// </summary>
public void SetPressure(double pressure)
public void SetPressure(double pressure, double? temperature = null)
{
double V = Math.Max(Volume, 1e-12);
double currentT = Temperature; // current temperature before changes
double rho = pressure / (GasConstant * currentT);
double T = temperature ?? Temperature;
double rho = pressure / (GasConstant * T);
Mass = rho * V;
InternalEnergy = pressure * V / (Gamma - 1.0);
}

View File

@@ -4,27 +4,18 @@ using FluidSim.Interfaces;
namespace FluidSim.Core
{
/// <summary>
/// Connects a port (volume or atmosphere) to one end of a pipe via an orifice.
/// Uses the isentropic nozzle model for the steadystate relationship,
/// and includes acoustic inertance for dynamic (Helmholtz) behaviour.
/// </summary>
public class OrificeLink
{
public Port VolumePort { get; }
public Port? VolumePort { get; }
public Pipe1D Pipe { get; }
public bool IsPipeLeftEnd { get; }
public Func<double> AreaProvider { get; set; }
public double DischargeCoefficient { get; set; } = 0.62;
// Acoustic length (wall thickness + end correction) controls the resonance frequency
public double EffectiveLength { get; set; } = 0.001; // 1 mm
// Whether to include inertance; if false, uses the steadystate nozzle model directly
public double EffectiveLength { get; set; } = 0.001;
public bool UseInertance { get; set; } = true;
// Current mass flow (kg/s, positive = volume → pipe)
private double _mdot;
private double _mdot; // positive = volume → pipe
public double LastMassFlowRate { get; private set; }
public double LastFaceDensity { get; private set; }
@@ -33,7 +24,7 @@ namespace FluidSim.Core
public OrificeLink(Port? volumePort, Pipe1D pipe, bool isPipeLeftEnd, Func<double> areaProvider)
{
VolumePort = volumePort; // null is allowed
VolumePort = volumePort;
Pipe = pipe ?? throw new ArgumentNullException(nameof(pipe));
IsPipeLeftEnd = isPipeLeftEnd;
AreaProvider = areaProvider ?? throw new ArgumentNullException(nameof(areaProvider));
@@ -43,20 +34,18 @@ namespace FluidSim.Core
public void Resolve(double dtSub)
{
double area = AreaProvider();
// Closed wall or missing volume port => reflective boundary
if (area < 1e-12 || VolumePort == null)
{
SetClosedWall();
return;
}
// Gather volume state
// Gather states
double volP = VolumePort.Pressure;
double volRho = VolumePort.Density;
double volT = VolumePort.Temperature;
double volH = VolumePort.SpecificEnthalpy;
// Gather pipe interior state at the connected end
(double pipeRho, double pipeU, double pipeP) = IsPipeLeftEnd
? Pipe.GetInteriorStateLeft()
: Pipe.GetInteriorStateRight();
@@ -65,24 +54,23 @@ namespace FluidSim.Core
double gamma = 1.4;
double R = 287.0;
// ---- Steadystate mass flow from isentropic nozzle ----
double mdotSS; // positive = volume → pipe
double rhoFace, uFace, pFace;
// ---- 1. Steadystate nozzle solution (gives correct exit pressure) ----
double mdotSS;
double rhoFace0, uFace0, pFace0;
if (volP >= pipeP)
{
IsentropicOrifice.Compute(volP, volRho, volT, pipeP, gamma, R, area, DischargeCoefficient,
out double mdotUpToDown, out rhoFace, out uFace, out pFace);
out double mdotUpToDown, out rhoFace0, out uFace0, out pFace0);
mdotSS = mdotUpToDown; // volume → pipe
}
else
{
IsentropicOrifice.Compute(pipeP, pipeRho, pipeT, volP, gamma, R, area, DischargeCoefficient,
out double mdotUpToDown, out rhoFace, out uFace, out pFace);
out double mdotUpToDown, out rhoFace0, out uFace0, out pFace0);
mdotSS = -mdotUpToDown; // pipe → volume → negative for volume→pipe convention
}
// ---- Inertance ODE (optional) ----
// ---- 2. Inertance dynamics ----
if (UseInertance)
{
double rhoUp = _mdot >= 0 ? volRho : pipeRho;
@@ -97,35 +85,31 @@ namespace FluidSim.Core
_mdot = mdotSS;
}
// Clamp outflow to available mass (if finite volume)
// Clamp outflow to available mass
if (VolumePort.Owner is Volume0D vol)
{
double maxOut = vol.Mass / dtSub;
if (_mdot > maxOut) _mdot = maxOut;
}
// ---- Ghost state ----
// Density = upstream density (consistent with current flow direction)
rhoFace = _mdot >= 0 ? volRho : pipeRho;
// Pressure = downstream pressure (consistent with nozzle exit)
pFace = _mdot >= 0 ? pipeP : volP;
// Velocity magnitude derived from actual mass flow
// ---- 3. Ghost state (use nozzleexit pressure!) ----
double rhoFace = _mdot >= 0 ? volRho : pipeRho; // upstream density
double pFace = pFace0; // correct exit pressure (choked/subsonic)
double mdotMag = Math.Abs(_mdot);
uFace = mdotMag / (rhoFace * area);
double uFace = mdotMag / (rhoFace * area);
if (IsPipeLeftEnd)
uFace = _mdot >= 0 ? uFace : -uFace; // left end: positive u = into pipe
uFace = _mdot >= 0 ? uFace : -uFace; // left: +u into pipe
else
uFace = _mdot >= 0 ? -uFace : uFace; // right end: positive u = out of pipe
uFace = _mdot >= 0 ? -uFace : uFace; // right: +u out of pipe
// Apply ghost to pipe
if (IsPipeLeftEnd)
Pipe.SetGhostLeft(rhoFace, uFace, pFace);
else
Pipe.SetGhostRight(rhoFace, uFace, pFace);
// ---- Store results ----
double mdotIntoVolume = -_mdot; // positive = into volume
// Store for monitoring
double mdotIntoVolume = -_mdot;
LastMassFlowRate = mdotIntoVolume;
LastFaceDensity = rhoFace;
LastFaceVelocity = uFace;
@@ -133,13 +117,12 @@ namespace FluidSim.Core
VolumePort.MassFlowRate = mdotIntoVolume;
// Enthalpy for volume integration
if (mdotIntoVolume >= 0) // inflow → pipe enthalpy
if (mdotIntoVolume >= 0)
{
double hPipe = gamma / (gamma - 1.0) * pipeP / Math.Max(pipeRho, 1e-12);
VolumePort.SpecificEnthalpy = hPipe;
}
else // outflow → volume's own enthalpy
else
{
VolumePort.SpecificEnthalpy = volH;
}
@@ -160,7 +143,6 @@ namespace FluidSim.Core
LastFaceDensity = rInt;
LastFaceVelocity = 0.0;
LastFacePressure = pInt;
// Don't touch VolumePort if it's null
if (VolumePort != null)
VolumePort.MassFlowRate = 0.0;
}

View File

@@ -10,7 +10,13 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SFML.Net" Version="3.0.0" />
<PackageReference Include="SFML.Net" Version="3.0.0" />
</ItemGroup>
<ItemGroup>
<None Update="fonts\LiberationMono-Regular.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -13,9 +13,8 @@ public class Program
private const double DrawFrequency = 60.0;
private static Scenario scenario;
// Speed control (existing + new throttle)
// Speed control
private static double desiredSpeed = 0.01;
//private static double desiredSpeed = 1.0;
private static double currentSpeed = desiredSpeed;
private const double MinSpeed = 0.0001;
private const double MaxSpeed = 1.0;
@@ -24,22 +23,47 @@ public class Program
private static double lastDesiredSpeed = 0.1;
private static bool isRealTime = false;
// Throttle smoothing
private static double targetThrottle = 0.0; // 1.0 when W is pressed, 0.0 otherwise
// Throttle smoothing (unused but kept)
private static double targetThrottle = 0.0;
private static double currentThrottle = 0.0;
private const double ThrottleSmoothing = 20.0; // rate of change
private const double ThrottleSmoothing = 20.0;
private static volatile bool running = true;
// ---- Overlay text ----
private static Font? overlayFont;
private static Text? overlayText;
public static void Main()
{
var mode = new VideoMode(new Vector2u(1280, 720));
var window = new RenderWindow(mode, "FluidSim - Engine (W = throttle)");
var window = new RenderWindow(mode, "FluidSim");
window.SetVerticalSyncEnabled(true);
window.Closed += (_, _) => { running = false; window.Close(); };
window.MouseWheelScrolled += OnMouseWheel;
window.KeyPressed += OnKeyPressed;
// ---- Load font ----
try
{
overlayFont = new Font("fonts/FiraCodeNerdFont-Medium.ttf");
}
catch (Exception ex)
{
Console.WriteLine($"Failed to load font 'fonts/LiberationMono-Regular.ttf': {ex.Message}");
overlayFont = null; // will skip text drawing
}
if (overlayFont != null)
{
// SFML 3 Text(font, character size in pixels)
overlayText = new Text(overlayFont)
{
FillColor = Color.White,
Position = new Vector2f(10, 10)
};
}
var soundEngine = new SoundEngine(bufferCapacity: 16384);
soundEngine.Volume = 100;
soundEngine.Start();
@@ -74,7 +98,6 @@ public class Program
double speedSmoothing = 8.0;
currentSpeed += (desiredSpeed - currentSpeed) * (1.0 - Math.Exp(-speedSmoothing * dtClock));
// Generate audio
double targetAudioClock = currentRealTime + 0.05;
while (totalOutputSamples < targetAudioClock * SampleRate && running)
@@ -116,21 +139,30 @@ public class Program
break;
}
// Drawing & title
// Drawing
if (currentRealTime - lastDrawTime >= drawInterval)
{
double actualSpeed = totalOutputSamples / (currentRealTime * SampleRate);
double simTime = totalSimSteps / (double)SampleRate;
string toggleHint = isRealTime ? "[Space] slow mo" : "[Space] real time";
string throttleHint = Keyboard.IsKeyPressed(Keyboard.Key.W) ? "W held" : "W released";
window.SetTitle(
$"{toggleHint} {throttleHint} " +
$"Thr: {currentThrottle:F2} " +
$"Speed: {currentSpeed:F3}x → {desiredSpeed:F3}x " +
$"Act: {actualSpeed:F2}x"
);
double realtimePercent = totalOutputSamples / (currentRealTime * SampleRate) * 100.0;
// Update overlay text
if (overlayText != null)
{
string toggleHint = isRealTime ? "[Space] slow mo" : "[Space] real time";
string throttleHint = Keyboard.IsKeyPressed(Keyboard.Key.W) ? "W held" : "W released";
overlayText.DisplayedString =
$"{toggleHint} {throttleHint} " +
$"Speed: {currentSpeed:F3}x " +
$"RT: {realtimePercent:F1}%";
}
window.Clear(Color.Black);
scenario.Draw(window);
// Draw the overlay on top
if (overlayText != null)
window.Draw(overlayText);
window.Display();
lastDrawTime = currentRealTime;
}
@@ -140,7 +172,6 @@ public class Program
window.Dispose();
}
// (Mouse wheel, space toggle unchanged)
private static void OnMouseWheel(object? sender, MouseWheelScrollEventArgs e)
{
bool wasRealTime = Math.Abs(desiredSpeed - 1.0) < 1e-6;

View File

@@ -19,34 +19,37 @@ namespace FluidSim.Tests
// ---------- Shared drawing helpers ----------
protected const double AmbientPressure = 101325.0;
protected const double AmbientTemperature = 300.0; // K
/// <summary>Blue (low) → Green (ambient) → Red (high).</summary>
protected Color PressureColor(double pressure)
/// <summary>Map temperature [0K … 2000K] to a color: blue (0K) → green (300K) → red (2000K).</summary>
protected Color TemperatureColor(double temperature)
{
double range = AmbientPressure * 0.05; // ±5% gives full colour swing
double t = (pressure - AmbientPressure) / range;
t = Math.Clamp(t, -1.0, 1.0);
// Clamp to the range we want to display
double t = Math.Clamp(temperature, 0.0, 2000.0);
byte r, g, b;
if (t < 0)
if (t < AmbientTemperature)
{
double factor = -t;
// Blue → Green
double factor = t / AmbientTemperature; // 0 at 0K, 1 at 300K
r = 0;
g = (byte)(255 * (1 - factor));
b = (byte)(255 * factor);
g = (byte)(255 * factor);
b = (byte)(255 * (1.0 - factor));
}
else
{
double factor = t;
// Green → Red
double factor = (t - AmbientTemperature) / (2000.0 - AmbientTemperature); // 0 at 300K, 1 at 2000K
r = (byte)(255 * factor);
g = (byte)(255 * (1 - factor));
g = (byte)(255 * (1.0 - factor));
b = 0;
}
return new Color(r, g, b);
}
/// <summary>
/// Draws the pipe as a smooth trianglestrip whose radius varies with cell pressure.
/// Draws the pipe as a smooth trianglestrip whose radius varies with cell pressure (for visibility),
/// but colored by temperature.
/// </summary>
protected void DrawPipe(RenderWindow target, Pipe1D pipe, float pipeCenterY, float pipeStartX, float pipeEndX)
{
@@ -57,8 +60,8 @@ namespace FluidSim.Tests
float dx = pipeLengthPx / (n - 1); // spacing between cell centres
float baseRadius = 25f;
float rangeFactor = 1f;
float scaleFactor = 5f;
float rangeFactor = 2f;
float scaleFactor = 2f;
// ----- smoothstep helper -----
static float SmoothStep(float edge0, float edge1, float x)
@@ -67,12 +70,19 @@ namespace FluidSim.Tests
return t * t * (3f - 2f * t);
}
// ----- Precompute cell positions and radii -----
// ----- Precompute cell positions, radii, and temperatures -----
var centers = new float[n];
var radii = new float[n];
var temperatures = new double[n];
double R_gas = 287.0;
for (int i = 0; i < n; i++)
{
double p = pipe.GetCellPressure(i);
double rho = pipe.GetCellDensity(i);
double T = p / Math.Max(rho * R_gas, 1e-12); // ideal gas
temperatures[i] = T;
float deviation = (float)Math.Tanh((p - AmbientPressure) / AmbientPressure / rangeFactor);
radii[i] = baseRadius * (1f + deviation * scaleFactor);
if (radii[i] < 2f) radii[i] = 2f;
@@ -89,8 +99,7 @@ namespace FluidSim.Tests
{
float x = centers[i];
float r = radii[i];
double p = pipe.GetCellPressure(i);
Color col = PressureColor(p);
Color col = TemperatureColor(temperatures[i]);
stripVertices[idx++] = new Vertex(new Vector2f(x, pipeCenterY - r), col);
stripVertices[idx++] = new Vertex(new Vector2f(x, pipeCenterY + r), col);
@@ -103,8 +112,8 @@ namespace FluidSim.Tests
float st = SmoothStep(0f, 1f, t);
float xi = centers[i] + (centers[i + 1] - centers[i]) * t;
float ri = radii[i] + (radii[i + 1] - radii[i]) * st;
double pi = pipe.GetCellPressure(i) * (1 - t) + pipe.GetCellPressure(i + 1) * t;
Color coli = PressureColor(pi);
double Ti = temperatures[i] + (temperatures[i + 1] - temperatures[i]) * st; // linear interpolation
Color coli = TemperatureColor(Ti);
stripVertices[idx++] = new Vertex(new Vector2f(xi, pipeCenterY - ri), coli);
stripVertices[idx++] = new Vertex(new Vector2f(xi, pipeCenterY + ri), coli);

View File

@@ -9,124 +9,222 @@ namespace FluidSim.Tests
{
public class TestScenario : Scenario
{
// Simulation core
private Solver solver;
private Volume0D volume;
private Pipe1D pipe;
private OrificeLink orifice; // volume → pipe left
private OpenEndLink openEnd; // pipe right → atmosphere
private SoundProcessor soundProcessor;
private int stepCount;
// Pressure reset scheduling
private double simTime;
private double dt;
private double resetInterval = 0.2; // seconds between resets
private double nextResetTime;
private double targetPressure = 10 * Units.atm;
private double rampDuration = 0.002; // 2 ms ramp
private double rampStartTime;
private double rampStartPressure; // pressure when ramp started
private bool ramping;
// Engine components
private Volume0D cylinder;
private Pipe1D exhaustPipe;
private OrificeLink exhaustPort;
private OpenEndLink pipeOpenEnd;
private Crankshaft crankshaft;
// Audio
private SoundProcessor soundProcessor;
// Engine geometry (Suzuki TS125 Jones Appendix 1)
private const double Bore = 0.056; // m
private const double Stroke = 0.050; // m
private const double ConRodLength = 0.110; // m (typical)
private const double CrankRadius = Stroke / 2.0;
private const double Obliquity = CrankRadius / ConRodLength;
private const double CompressionRatio = 6.7; // from Jones
// Derived volumes
private double sweptVolume;
private double clearanceVolume;
// Port timing (degrees from TDC)
private const double ExhaustPortOpens = 98.0; // °ATDC
private const double ExhaustPortCloses = 262.0; // °ATDC
private const double PortWidth = 0.025; // m (estimated)
private const double MaxPortArea = 0.001; // m² (fully open)
// Engine state
private double crankAngle; // rad
private double engineSpeed; // rad/s
private bool combustionPending; // true when ready to fire at TDC
// Logging
private int stepCount;
public override void Initialize(int sampleRate)
{
dt = 1.0 / sampleRate;
soundProcessor = new SoundProcessor(sampleRate, 1);
soundProcessor.Gain = 2.0f; // lower gain to avoid clipping
// Audio
soundProcessor = new SoundProcessor(sampleRate, 1) { Gain = 1f };
// Solver
solver = new Solver();
solver.SetTimeStep(dt);
solver.CflTarget = 0.4;
solver.CflTarget = 0.4; // safe CFL for highpressure pulses
volume = new Volume0D(1e-3, targetPressure, 300.0);
solver.AddComponent(volume);
// Compute engine volumes
double boreArea = Math.PI * 0.25 * Bore * Bore;
sweptVolume = boreArea * Stroke;
clearanceVolume = sweptVolume / (CompressionRatio - 1.0);
double initialVolume = clearanceVolume; // at TDC
pipe = new Pipe1D(1.0, 1e-4, 200);
pipe.EnergyRelaxationRate = 10;
solver.AddComponent(pipe);
var volPort = volume.CreatePort();
double orificeArea = 1e-5;
orifice = new OrificeLink(volPort, pipe, isPipeLeftEnd: true,
areaProvider: () => orificeArea)
// Cylinder
cylinder = new Volume0D(initialVolume, 101325.0, 300.0)
{
DischargeCoefficient = 0.62,
UseInertance = true,
EffectiveLength = 0.001
Dvdt = 0.0
};
solver.AddOrificeLink(orifice);
solver.AddComponent(cylinder);
openEnd = new OpenEndLink(pipe, isLeftEnd: false)
// Exhaust pipe (1 m, 1 cm², 100 cells)
exhaustPipe = new Pipe1D(0.5, 10e-4, 20);
solver.AddComponent(exhaustPipe);
// Exhaust port orifice with variable area
var cylPort = cylinder.CreatePort();
exhaustPort = new OrificeLink(cylPort, exhaustPipe, isPipeLeftEnd: true,
areaProvider: () => ComputeExhaustPortArea(crankAngle))
{
DischargeCoefficient = 0.8,
UseInertance = false
};
solver.AddOrificeLink(exhaustPort);
// Pipe open end
pipeOpenEnd = new OpenEndLink(exhaustPipe, isLeftEnd: false)
{
AmbientPressure = 101325.0,
Gamma = 1.4
};
solver.AddOpenEndLink(openEnd);
solver.AddOpenEndLink(pipeOpenEnd);
// Crankshaft (3000 rpm)
crankshaft = new Crankshaft(initialRPM: 10000.0);
crankAngle = crankshaft.CrankAngle;
engineSpeed = crankshaft.AngularVelocity;
combustionPending = false; // first combustion will occur at next TDC
stepCount = 0;
simTime = 0.0;
nextResetTime = resetInterval;
ramping = false;
Console.WriteLine("Pressure reset test with smooth ramp");
Console.WriteLine($"Volume 1L, reset to {targetPressure} Pa every {resetInterval*1000} ms, ramp {rampDuration*1000} ms");
Console.WriteLine("2Stroke engine test");
Console.WriteLine($"Engine: {Bore*1000:F0} mm x {Stroke*1000:F0} mm, {sweptVolume*1e6:F0} cc");
Console.WriteLine($"Compression ratio: {CompressionRatio:F1}, clearance volume: {clearanceVolume*1e6:F2} cc");
Console.WriteLine($"Exhaust port opens at {ExhaustPortOpens}° ATDC, closes at {ExhaustPortCloses}° ATDC");
}
// ---- Port area vs crank angle (linear ramp, symmetric) ----
private double ComputeExhaustPortArea(double thetaRad)
{
double thetaDeg = thetaRad * 180.0 / Math.PI;
// Wrap to [0,360) for easier logic
thetaDeg %= 360.0;
// Exhaust open period
if (thetaDeg >= ExhaustPortOpens && thetaDeg <= ExhaustPortCloses)
{
// Ramp up from 0 to Max, then back down
double halfPeriod = (ExhaustPortCloses - ExhaustPortOpens) / 2.0;
double midPoint = ExhaustPortOpens + halfPeriod;
double distFromMid = Math.Abs(thetaDeg - midPoint) / halfPeriod;
double fraction = 1.0 - distFromMid;
fraction = Math.Clamp(fraction, 0.0, 1.0);
return MaxPortArea * fraction;
}
return 0.0;
}
// ---- Cylinder volume vs crank angle (slidercrank) ----
private double ComputeCylinderVolume(double thetaRad)
{
// thetaRad = crank angle from TDC (0 at TDC)
double r = CrankRadius;
double l = ConRodLength;
double cosTh = Math.Cos(thetaRad);
double sinTh = Math.Sin(thetaRad);
double term = Math.Sqrt(1.0 - Obliquity * Obliquity * sinTh * sinTh);
double x = r * (1.0 - cosTh) + l * (1.0 - term);
double area = Math.PI * 0.25 * Bore * Bore;
double deltaV = area * x;
return clearanceVolume + deltaV;
}
// ---- Combustion: set cylinder pressure AND temperature ----
private void Combustion()
{
double peakPressure = 20.0 * Units.atm; // 30 bar
double peakTemperature = 2000.0; // K
cylinder.SetPressure(peakPressure, peakTemperature);
}
public override float Process()
{
// Previous crank angle for detecting TDC crossing
double prevAngle = crankshaft.CrankAngle;
// Advance crankshaft
crankshaft.Step(dt);
crankAngle = crankshaft.CrankAngle;
engineSpeed = crankshaft.AngularVelocity;
// Update cylinder volume to match current crank angle
double newVolume = ComputeCylinderVolume(crankAngle);
cylinder.Dvdt = (newVolume - cylinder.Volume) / dt;
cylinder.Volume = newVolume;
// ----- Ignition (once per revolution at TDC) -----
const double TwoPi = 2.0 * Math.PI;
double prevMod = prevAngle % TwoPi;
double currMod = crankAngle % TwoPi;
// Detect crossing of 0 mod 2π (TDC) going from near 2π to near 0
if (prevMod > Math.PI * 1.8 && currMod < Math.PI * 0.2)
{
if (!combustionPending)
{
Combustion();
combustionPending = true; // prevent multiple firings during the crossing
}
}
else if (currMod > Math.PI * 0.2 && currMod < Math.PI * 1.8)
{
combustionPending = false; // reset flag once clear of TDC
}
// Run solver
solver.Step();
stepCount++;
simTime += dt;
// ---- Smooth pressure ramp ----
if (ramping)
{
double elapsed = simTime - rampStartTime;
if (elapsed >= rampDuration)
{
// Ramp finished, set exactly to target
volume.SetPressure(targetPressure);
ramping = false;
}
else
{
double frac = elapsed / rampDuration;
double currentTarget = rampStartPressure + (targetPressure - rampStartPressure) * frac;
volume.SetPressure(currentTarget);
}
}
// ---- Trigger a new reset ----
if (!ramping && simTime >= nextResetTime)
{
rampStartPressure = volume.Pressure; // current pressure before reset
rampStartTime = simTime;
ramping = true;
nextResetTime += resetInterval;
}
// Log every 500 steps
if (stepCount % 500 == 0)
if (stepCount % 50000 == 0)
{
double volP = volume.Pressure;
double pipeL = pipe.GetCellPressure(0);
double pipeR = pipe.GetCellPressure(pipe.CellCount - 1);
double mdotOrif = orifice.LastMassFlowRate;
double mdotOpen = openEnd.LastMassFlowRate;
int midCell = exhaustPipe.CellCount / 2;
Console.WriteLine($"Step {stepCount}: " +
$"VolP={volP:F1} Pa, PipeL={pipeL:F1}, PipeR={pipeR:F1}, " +
$"mdot_orif={mdotOrif:E4} kg/s, mdot_open={mdotOpen:E4} kg/s");
double cylP_bar = cylinder.Pressure / 1e5;
double cylT_K = cylinder.Temperature;
double cylVol_cc = cylinder.Volume * 1e6;
double pipeL_bar = exhaustPipe.GetCellPressure(0) / 1e5;
double pipeM_bar = exhaustPipe.GetCellPressure(midCell) / 1e5;
double pipeR_bar = exhaustPipe.GetCellPressure(exhaustPipe.CellCount - 1) / 1e5;
double mdotExh = exhaustPort.LastMassFlowRate; // kg/s, positive into cylinder
double mdotOpen = pipeOpenEnd.LastMassFlowRate; // kg/s, positive out
Console.WriteLine(
$"Step {stepCount}: Angle={crankAngle*180.0/Math.PI % 360.0:F1}°, " +
$"CylP={cylP_bar:F2} bar, CylT={cylT_K:F0} K, Vol={cylVol_cc:F1} cc, " +
$"PipeL={pipeL_bar:F2} bar, PipeM={pipeM_bar:F2} bar, PipeR={pipeR_bar:F2} bar, " +
$"mdot_exh={mdotExh:E4} kg/s, mdot_open={mdotOpen:E4} kg/s"
);
}
if (double.IsNaN(pipe.GetCellPressure(0)))
if (double.IsNaN(exhaustPipe.GetCellPressure(0)))
{
Console.WriteLine("NaN detected stopping.");
return 0f;
}
return soundProcessor.Process(openEnd);
// Audio from open end
return soundProcessor.Process(pipeOpenEnd);
}
public override void Draw(RenderWindow target)
@@ -137,7 +235,7 @@ namespace FluidSim.Tests
float margin = 60f;
float pipeStartX = margin;
float pipeEndX = winWidth - margin;
DrawPipe(target, pipe, pipeCenterY, pipeStartX, pipeEndX);
DrawPipe(target, exhaustPipe, pipeCenterY, pipeStartX, pipeEndX);
}
}
}

Binary file not shown.

BIN
trace.nettrace Normal file

Binary file not shown.

BIN
trace.nettrace.etlx Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long