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

@@ -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);
}
}
}