Files
FluidSim/Scenarios/SingleCylScenario.cs

292 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using FluidSim.Components;
using FluidSim.Core;
using FluidSim.Interfaces;
using SFML.Graphics;
using SFML.System;
using System;
namespace FluidSim.Tests
{
public class SingleCylScenario : Scenario
{
// ---------- Engine components ----------
private Crankshaft crankshaft;
private Cylinder cylinder;
// ---------- Fluid network ----------
private PipeSystem pipeSystem;
private BoundarySystem boundaries;
private Solver solver;
// Volumes
private Volume0D intakePlenum;
// Ports
private Port plenumInlet, plenumOutlet;
// Orifice / openend indices
private int throttleAreaIdx, plenumRunnerIdx, intakeValveIdx, exhaustValveIdx;
private int intakeOpenIdx, exhaustOpenIdx;
private float[] orificeAreas;
// Sound
private SoundProcessor exhaustSound, intakeSound;
private OutdoorExhaustReverb reverb;
// ---------- Simulation state ----------
private double dt;
private int stepCount;
public float MaxThrottleArea = 100e-4f; // 1 cm²
// ---------- Geometry (Lifan YX140) ----------
// Bore 56 mm, Stroke 57 mm, CR 9.5
private const float Bore = 0.056f;
private const float Stroke = 0.057f;
private const float ConRod = 0.110f; // typical for 57 mm stroke
private const float CompressionRatio = 9.5f;
// Valve diameters (intake 27 mm, exhaust 23 mm)
private const float IntakeValveDiam = 0.027f;
private const float ExhaustValveDiam = 0.023f;
private const float ValveLift = 0.006f; // 6 mm peak lift
// Valve timings (degrees, 720° fourstroke)
// Intake: 15° BTDC → 45° ABDC
private const float IVO = 345f; // 15° BTDC
private const float IVC = 585f; // 45° ABDC (180°+45°)
// Exhaust: 45° BBDC → 15° ATDC
private const float EVO = 135f; // 45° BBDC (180°-45°)
private const float EVC = 375f; // 15° ATDC (360°+15°)
// Spark advance: 30° BTDC
private const float SparkAdv = 30f;
// Pipe / plenum sizes
private const float PipeDiam = 0.025f; // 25 mm intake / exhaust
private const float PipeArea = 0.00049087f; // π*D²/4
private const float PlenumVolume = 0.0005f; // 500 mL
private const float MaxThrottleArea = 1e-4f; // ~1 cm² (fully open)
// Pipe lengths and cell counts
private const float IntakeLenBefore = 0.15f; // 15 cm before throttle
private const float RunnerLen = 0.25f; // 25 cm runner
private const float ExhaustLen = 0.60f; // 60 cm exhaust
private const int CellsBefore = 6;
private const int CellsRunner = 10;
private const int CellsExhaust = 24;
public override void Initialize(int sampleRate)
{
dt = 1.0 / sampleRate;
// ---- Crankshaft ----
crankshaft = new Crankshaft(600);
crankshaft.Inertia = 0.05f;
crankshaft.FrictionConstant = 2f;
crankshaft.FrictionViscous = 0.01f;
// ---------- Cylinder ----------
cylinder = new Cylinder(Bore, Stroke, ConRod, CompressionRatio,
IVO, IVC, EVO, EVC, crankshaft)
{
IntakeValveDiameter = IntakeValveDiam,
ExhaustValveDiameter = ExhaustValveDiam,
IntakeValveLift = ValveLift,
ExhaustValveLift = ValveLift,
SparkAdvance = SparkAdv,
EnergyVariationFraction = 0.03f, // small cycletocycle variation
MisfireProbability = 0.0f
};
// ---------- Pipe system ----------
int totalCells = CellsBefore + CellsRunner + CellsExhaust;
int[] pipeStart = { 0, CellsBefore, CellsBefore + CellsRunner };
int[] pipeEnd = { CellsBefore, CellsBefore + CellsRunner, totalCells };
float[] areas = new float[totalCells];
float[] dxs = new float[totalCells];
float dxBefore = IntakeLenBefore / CellsBefore;
float dxRunner = RunnerLen / CellsRunner;
float dxExh = ExhaustLen / CellsExhaust;
for (int i = 0; i < totalCells; i++)
{
areas[i] = PipeArea;
if (i < CellsBefore)
dxs[i] = dxBefore;
else if (i < CellsBefore + CellsRunner)
dxs[i] = dxRunner;
else
dxs[i] = dxExh;
}
pipeSystem = new PipeSystem(totalCells, pipeStart, pipeEnd, area, dx,
1.225f, 0f, 101325f);
pipeSystem.DampingMultiplier = 1.0f;
pipeSystem.EnergyRelaxationRate = 0.5f;
pipeSystem.AmbientPressure = 101325f;
// ---- Volumes ----
intakePlenum = new Volume0D(100e-6f, 101325f, 300f); // 100 mL
plenumInlet = intakePlenum.CreatePort();
plenumOutlet = intakePlenum.CreatePort();
// ---------- Boundary system ----------
boundaries = new BoundarySystem(pipeSystem, maxOrifices: 4, maxOpenEnds: 2);
throttleAreaIdx = 0;
plenumRunnerIdx = 1;
intakeValveIdx = 2;
exhaustValveIdx = 3;
// Open ends
boundaries.AddOpenEnd(pipeIndex: 0, isLeftEnd: true, 101325f, PipeArea);
intakeOpenIdx = 0;
boundaries.AddOpenEnd(pipeIndex: 2, isLeftEnd: false, 101325f, PipeArea);
exhaustOpenIdx = 1;
// Orifices
// throttle variable area, low discharge for restriction
boundaries.AddOrifice(plenumInlet, pipeIndex: 0, isLeftEnd: false,
throttleAreaIdx, dischargeCoeff: 0.8f);
// plenum → runner
boundaries.AddOrifice(plenumOutlet, pipeIndex: 1, isLeftEnd: true,
plenumRunnerIdx, dischargeCoeff: 1.0f);
// intake valve
boundaries.AddOrifice(cylinder.IntakePort, pipeIndex: 1, isLeftEnd: false,
intakeValveIdx, dischargeCoeff: 1.0f);
// exhaust valve
boundaries.AddOrifice(cylinder.ExhaustPort, pipeIndex: 2, isLeftEnd: true,
exhaustValveIdx, dischargeCoeff: 1.0f);
orificeAreas = new float[4];
orificeAreas[plenumRunnerIdx] = PipeArea; // fixed fullbore
// ---------- Solver ----------
solver = new Solver { SubStepCount = 5, EnableProfiling = false };
solver.SetTimeStep(dt);
solver.SetPipeSystem(pipeSystem);
solver.SetBoundarySystem(boundaries);
solver.AddComponent(cylinder);
solver.AddComponent(intakePlenum);
// ---- Sound ----
exhaustSound = new SoundProcessor(sampleRate, 1f) { Gain = 20f };
intakeSound = new SoundProcessor(sampleRate, 1f) { Gain = 20f };
reverb = new OutdoorExhaustReverb(sampleRate);
stepCount = 0;
Console.WriteLine("Singlecylinder engine (YX140) ready.");
}
public override float Process()
{
// ---- Crank and cylinder prestep ----
crankshaft.Step((float)dt);
cylinder.PreStep((float)dt);
// ---- Update variable areas ----
float throttledArea = MaxThrottleArea * Math.Clamp(Throttle, 0.0001f, 1.0f);
orificeAreas[throttleAreaIdx] = throttledArea;
orificeAreas[intakeValveIdx] = cylinder.IntakeValveArea;
orificeAreas[exhaustValveIdx] = cylinder.ExhaustValveArea;
boundaries.SetOrificeAreas(orificeAreas);
// ---- Fluids step ----
solver.Step();
stepCount++;
// ---- Sound ----
float exhaustFlow = boundaries.GetOpenEndMassFlow(exhaustOpenIdx);
float intakeFlow = boundaries.GetOpenEndMassFlow(intakeOpenIdx);
float exhaustDry = exhaustSound.Process(exhaustFlow);
float intakeDry = intakeSound.Process(intakeFlow);
if (stepCount % 2000 == 0)
{
float rpm = crankshaft.AngularVelocity * 60f / (2f * MathF.PI);
float crankDeg = crankshaft.CrankAngle; // public property on Cylinder
Console.WriteLine($"Step {stepCount}, CA={crankDeg:F1} deg, RPM={rpm:F0}, CylP={cylinder.Pressure / 1e5f:F2} bar");
Console.WriteLine($" intake flow: {intakeFlow:F6}, exhaust flow: {exhaustFlow:F6}");
// Pipe 0 (intake before throttle)
var (r0L, u0L, p0L) = pipeSystem.GetInteriorStateLeft(0);
var (r0R, u0R, p0R) = pipeSystem.GetInteriorStateRight(0);
Console.WriteLine($" Pipe0 L: rho={r0L:F4} u={u0L:F3} p={p0L/1e5:F3}bar | R: rho={r0R:F4} u={u0R:F3} p={p0R/1e5:F3}bar");
// Pipe 1 (runner)
var (r1L, u1L, p1L) = pipeSystem.GetInteriorStateLeft(1);
var (r1R, u1R, p1R) = pipeSystem.GetInteriorStateRight(1);
Console.WriteLine($" Pipe1 L: rho={r1L:F4} u={u1L:F3} p={p1L/1e5:F3}bar | R: rho={r1R:F4} u={u1R:F3} p={p1R/1e5:F3}bar");
// Pipe 2 (exhaust)
var (r2L, u2L, p2L) = pipeSystem.GetInteriorStateLeft(2);
var (r2R, u2R, p2R) = pipeSystem.GetInteriorStateRight(2);
Console.WriteLine($" Pipe2 L: rho={r2L:F4} u={u2L:F3} p={p2L/1e5:F3}bar | R: rho={r2R:F4} u={u2R:F3} p={p2R/1e5:F3}bar");
// Plenum and cylinder mass
Console.WriteLine($" Plenum P={intakePlenum.Pressure/1e5:F3}bar, mass={intakePlenum.Mass:E4} kg");
Console.WriteLine($" Cyl mass={cylinder.Mass:E4} kg");
}
return reverb.Process(intakeDry + exhaustDry);
}
public override void Draw(RenderWindow target)
{
float winW = target.GetView().Size.X;
float winH = target.GetView().Size.Y;
float intakeY = winH / 2f - 40f;
float exhaustY = winH / 2f + 80f;
float leftX = 40f;
// Intake open end marker
var om = new CircleShape(5f) { FillColor = Color.Cyan };
om.Position = new Vector2f(leftX - 5f, intakeY - 5f);
target.Draw(om);
// Pipe 0 before throttle
float p0EndX = leftX + 80f;
DrawPipe(target, pipeSystem, 0, intakeY, leftX, p0EndX);
// Throttle symbol
float thrX = p0EndX + 5f;
var thr = new RectangleShape(new Vector2f(8f, 30f))
{
FillColor = Color.Yellow,
Position = new Vector2f(thrX, intakeY - 15f)
};
target.Draw(thr);
// Plenum volume
float plenW = 60f, plenH = 50f;
float plenLeftX = thrX + 12f;
float plenCenterX = plenLeftX + plenW / 2f;
float plenTopY = intakeY - plenH / 2f;
DrawVolume(target, intakePlenum, plenCenterX, plenTopY, plenW, plenH);
// Pipe 1 runner
float rStartX = plenLeftX + plenW + 10f;
float rEndX = rStartX + 100f;
DrawPipe(target, pipeSystem, 1, intakeY, rStartX, rEndX);
// Cylinder
float cylCX = rEndX + 50f;
float cylTopY = intakeY - 120f;
float cylW = 80f, cylMaxH = 240f;
DrawCylinder(target, cylinder, cylCX, cylTopY, cylW, cylMaxH);
// Pipe 2 exhaust
float exhStartX = cylCX + cylW / 2f + 20f;
float exhEndX = winW - 60f;
DrawPipe(target, pipeSystem, 2, exhaustY, exhStartX, exhEndX);
// Exhaust open end
var em = new CircleShape(5f) { FillColor = Color.Magenta };
em.Position = new Vector2f(exhEndX - 5f, exhaustY - 5f);
target.Draw(em);
}
}
}