added 4 cyl
This commit is contained in:
303
Scenarios/Inline4Scenario.cs
Normal file
303
Scenarios/Inline4Scenario.cs
Normal file
@@ -0,0 +1,303 @@
|
||||
using System;
|
||||
using SFML.Graphics;
|
||||
using SFML.System;
|
||||
using FluidSim.Components;
|
||||
using FluidSim.Core;
|
||||
using FluidSim.Utils;
|
||||
|
||||
namespace FluidSim.Tests
|
||||
{
|
||||
public class Inline4Scenario : Scenario
|
||||
{
|
||||
// Crankshaft
|
||||
private Crankshaft crankshaft;
|
||||
|
||||
// Cylinders
|
||||
private Cylinder cyl1, cyl2, cyl3, cyl4;
|
||||
|
||||
// Intake
|
||||
private Pipe1D intakePipeBeforeThrottle;
|
||||
private Volume0D intakePlenum;
|
||||
|
||||
// Runners
|
||||
private Pipe1D runner1, runner2, runner3, runner4;
|
||||
|
||||
// Exhaust pipes
|
||||
private Pipe1D exh1, exh2, exh3, exh4;
|
||||
|
||||
// Links – intake
|
||||
private OpenEndLink intakeOpenEnd;
|
||||
private OrificeLink throttleOrifice;
|
||||
|
||||
// Plenum‑to‑runner orifices
|
||||
private OrificeLink plenumToRunner1, plenumToRunner2, plenumToRunner3, plenumToRunner4;
|
||||
|
||||
// Intake valves
|
||||
private OrificeLink intakeValve1, intakeValve2, intakeValve3, intakeValve4;
|
||||
|
||||
// Exhaust valves
|
||||
private OrificeLink exhaustValve1, exhaustValve2, exhaustValve3, exhaustValve4;
|
||||
|
||||
// Exhaust open ends
|
||||
private OpenEndLink exhaustOpenEnd1, exhaustOpenEnd2, exhaustOpenEnd3, exhaustOpenEnd4;
|
||||
|
||||
private Solver solver;
|
||||
private SoundProcessor exhaustSoundProcessor;
|
||||
private SoundProcessor intakeSoundProcessor;
|
||||
private OutdoorExhaustReverb reverb;
|
||||
private double dt;
|
||||
private int stepCount;
|
||||
|
||||
public double MaxThrottleArea { get; set; } = 3 * Units.cm2;
|
||||
|
||||
public override void Initialize(int sampleRate)
|
||||
{
|
||||
dt = 1.0 / sampleRate;
|
||||
|
||||
solver = new Solver();
|
||||
solver.SetTimeStep(dt);
|
||||
solver.CflTarget = 0.9;
|
||||
|
||||
// ---- Shared crankshaft ----
|
||||
crankshaft = new Crankshaft(800);
|
||||
crankshaft.Inertia = 1;
|
||||
crankshaft.FrictionConstant = 16;
|
||||
crankshaft.FrictionViscous = 0.5;
|
||||
|
||||
// ---- Cylinder geometry ----
|
||||
double bore = 0.056, stroke = 0.057, conRod = 0.110, compRatio = 9.2;
|
||||
double ivo = 350.0, ivc = 580.0, evo = 120.0, evc = 370.0;
|
||||
|
||||
// Firing order 1-3-4-2 → phase offsets in radians
|
||||
double phase0 = 0.0 * Math.PI / 180.0;
|
||||
double phase1 = 180.0 * Math.PI / 180.0;
|
||||
double phase2 = 540.0 * Math.PI / 180.0;
|
||||
double phase3 = 360.0 * Math.PI / 180.0;
|
||||
|
||||
cyl1 = new Cylinder(bore, stroke, conRod, compRatio, ivo, ivc, evo, evc, crankshaft)
|
||||
{
|
||||
IntakeValveDiameter = 30 * Units.mm,
|
||||
IntakeValveLift = 5 * Units.mm,
|
||||
ExhaustValveDiameter = 28 * Units.mm,
|
||||
ExhaustValveLift = 5 * Units.mm,
|
||||
PhaseOffset = phase0,
|
||||
EnergyVariationFraction = 0.03,
|
||||
MisfireProbability = 0.01
|
||||
};
|
||||
cyl2 = new Cylinder(bore, stroke, conRod, compRatio, ivo, ivc, evo, evc, crankshaft)
|
||||
{
|
||||
IntakeValveDiameter = 30 * Units.mm,
|
||||
IntakeValveLift = 5 * Units.mm,
|
||||
ExhaustValveDiameter = 28 * Units.mm,
|
||||
ExhaustValveLift = 5 * Units.mm,
|
||||
PhaseOffset = phase1,
|
||||
EnergyVariationFraction = 0.03,
|
||||
MisfireProbability = 0.01
|
||||
};
|
||||
cyl3 = new Cylinder(bore, stroke, conRod, compRatio, ivo, ivc, evo, evc, crankshaft)
|
||||
{
|
||||
IntakeValveDiameter = 30 * Units.mm,
|
||||
IntakeValveLift = 5 * Units.mm,
|
||||
ExhaustValveDiameter = 28 * Units.mm,
|
||||
ExhaustValveLift = 5 * Units.mm,
|
||||
PhaseOffset = phase2,
|
||||
EnergyVariationFraction = 0.03,
|
||||
MisfireProbability = 0.01
|
||||
};
|
||||
cyl4 = new Cylinder(bore, stroke, conRod, compRatio, ivo, ivc, evo, evc, crankshaft)
|
||||
{
|
||||
IntakeValveDiameter = 30 * Units.mm,
|
||||
IntakeValveLift = 5 * Units.mm,
|
||||
ExhaustValveDiameter = 28 * Units.mm,
|
||||
ExhaustValveLift = 5 * Units.mm,
|
||||
PhaseOffset = phase3,
|
||||
EnergyVariationFraction = 0.03,
|
||||
MisfireProbability = 0.01
|
||||
};
|
||||
solver.AddComponent(cyl1);
|
||||
solver.AddComponent(cyl2);
|
||||
solver.AddComponent(cyl3);
|
||||
solver.AddComponent(cyl4);
|
||||
|
||||
double pipeDiameter = 2 * Units.cm;
|
||||
double pipeArea = Units.AreaFromDiameter(pipeDiameter);
|
||||
|
||||
exhaustSoundProcessor = new SoundProcessor(sampleRate, 1, pipeDiameter) { Gain = 0.1f };
|
||||
intakeSoundProcessor = new SoundProcessor(sampleRate, 1, pipeDiameter) { Gain = 0.1f };
|
||||
reverb = new OutdoorExhaustReverb(sampleRate);
|
||||
|
||||
// ---- Intake pipe before throttle ----
|
||||
intakePipeBeforeThrottle = new Pipe1D(0.2, pipeArea, 10);
|
||||
solver.AddComponent(intakePipeBeforeThrottle);
|
||||
|
||||
// ---- Plenum ----
|
||||
intakePlenum = new Volume0D(50 * Units.mL, 101325.0, 300.0);
|
||||
var plenumInlet = intakePlenum.CreatePort(); // port 0
|
||||
var plenumOut1 = intakePlenum.CreatePort(); // port 1
|
||||
var plenumOut2 = intakePlenum.CreatePort(); // port 2
|
||||
var plenumOut3 = intakePlenum.CreatePort(); // port 3
|
||||
var plenumOut4 = intakePlenum.CreatePort(); // port 4
|
||||
solver.AddComponent(intakePlenum);
|
||||
|
||||
// ---- Runners ----
|
||||
runner1 = new Pipe1D(0.2, pipeArea, 5);
|
||||
runner2 = new Pipe1D(0.2, pipeArea, 5);
|
||||
runner3 = new Pipe1D(0.2, pipeArea, 5);
|
||||
runner4 = new Pipe1D(0.2, pipeArea, 5);
|
||||
solver.AddComponent(runner1);
|
||||
solver.AddComponent(runner2);
|
||||
solver.AddComponent(runner3);
|
||||
solver.AddComponent(runner4);
|
||||
|
||||
// ---- Exhaust pipes ----
|
||||
exh1 = new Pipe1D(0.2, pipeArea, 10);
|
||||
exh2 = new Pipe1D(0.2, pipeArea, 10);
|
||||
exh3 = new Pipe1D(0.2, pipeArea, 10);
|
||||
exh4 = new Pipe1D(0.2, pipeArea, 10);
|
||||
solver.AddComponent(exh1);
|
||||
solver.AddComponent(exh2);
|
||||
solver.AddComponent(exh3);
|
||||
solver.AddComponent(exh4);
|
||||
|
||||
// ---- Plenum → runner orifices ----
|
||||
plenumToRunner1 = new OrificeLink(plenumOut1, runner1, isPipeLeftEnd: true, areaProvider: () => pipeArea) { DischargeCoefficient = 1.0, UseInertance = false };
|
||||
plenumToRunner2 = new OrificeLink(plenumOut2, runner2, isPipeLeftEnd: true, areaProvider: () => pipeArea) { DischargeCoefficient = 1.0, UseInertance = false };
|
||||
plenumToRunner3 = new OrificeLink(plenumOut3, runner3, isPipeLeftEnd: true, areaProvider: () => pipeArea) { DischargeCoefficient = 1.0, UseInertance = false };
|
||||
plenumToRunner4 = new OrificeLink(plenumOut4, runner4, isPipeLeftEnd: true, areaProvider: () => pipeArea) { DischargeCoefficient = 1.0, UseInertance = false };
|
||||
solver.AddOrificeLink(plenumToRunner1);
|
||||
solver.AddOrificeLink(plenumToRunner2);
|
||||
solver.AddOrificeLink(plenumToRunner3);
|
||||
solver.AddOrificeLink(plenumToRunner4);
|
||||
|
||||
// ---- Intake valves ----
|
||||
intakeValve1 = new OrificeLink(cyl1.IntakePort, runner1, isPipeLeftEnd: false, areaProvider: () => cyl1.IntakeValveArea) { DischargeCoefficient = 1.0, UseInertance = false };
|
||||
intakeValve2 = new OrificeLink(cyl2.IntakePort, runner2, isPipeLeftEnd: false, areaProvider: () => cyl2.IntakeValveArea) { DischargeCoefficient = 1.0, UseInertance = false };
|
||||
intakeValve3 = new OrificeLink(cyl3.IntakePort, runner3, isPipeLeftEnd: false, areaProvider: () => cyl3.IntakeValveArea) { DischargeCoefficient = 1.0, UseInertance = false };
|
||||
intakeValve4 = new OrificeLink(cyl4.IntakePort, runner4, isPipeLeftEnd: false, areaProvider: () => cyl4.IntakeValveArea) { DischargeCoefficient = 1.0, UseInertance = false };
|
||||
solver.AddOrificeLink(intakeValve1);
|
||||
solver.AddOrificeLink(intakeValve2);
|
||||
solver.AddOrificeLink(intakeValve3);
|
||||
solver.AddOrificeLink(intakeValve4);
|
||||
|
||||
// ---- Exhaust valves ----
|
||||
exhaustValve1 = new OrificeLink(cyl1.ExhaustPort, exh1, isPipeLeftEnd: true, areaProvider: () => cyl1.ExhaustValveArea) { DischargeCoefficient = 1.0, UseInertance = false };
|
||||
exhaustValve2 = new OrificeLink(cyl2.ExhaustPort, exh2, isPipeLeftEnd: true, areaProvider: () => cyl2.ExhaustValveArea) { DischargeCoefficient = 1.0, UseInertance = false };
|
||||
exhaustValve3 = new OrificeLink(cyl3.ExhaustPort, exh3, isPipeLeftEnd: true, areaProvider: () => cyl3.ExhaustValveArea) { DischargeCoefficient = 1.0, UseInertance = false };
|
||||
exhaustValve4 = new OrificeLink(cyl4.ExhaustPort, exh4, isPipeLeftEnd: true, areaProvider: () => cyl4.ExhaustValveArea) { DischargeCoefficient = 1.0, UseInertance = false };
|
||||
solver.AddOrificeLink(exhaustValve1);
|
||||
solver.AddOrificeLink(exhaustValve2);
|
||||
solver.AddOrificeLink(exhaustValve3);
|
||||
solver.AddOrificeLink(exhaustValve4);
|
||||
|
||||
// ---- Exhaust open ends ----
|
||||
exhaustOpenEnd1 = new OpenEndLink(exh1, isLeftEnd: false) { AmbientPressure = 101325.0, Gamma = 1.4 };
|
||||
exhaustOpenEnd2 = new OpenEndLink(exh2, isLeftEnd: false) { AmbientPressure = 101325.0, Gamma = 1.4 };
|
||||
exhaustOpenEnd3 = new OpenEndLink(exh3, isLeftEnd: false) { AmbientPressure = 101325.0, Gamma = 1.4 };
|
||||
exhaustOpenEnd4 = new OpenEndLink(exh4, isLeftEnd: false) { AmbientPressure = 101325.0, Gamma = 1.4 };
|
||||
solver.AddOpenEndLink(exhaustOpenEnd1);
|
||||
solver.AddOpenEndLink(exhaustOpenEnd2);
|
||||
solver.AddOpenEndLink(exhaustOpenEnd3);
|
||||
solver.AddOpenEndLink(exhaustOpenEnd4);
|
||||
|
||||
// ---- Intake open end ----
|
||||
intakeOpenEnd = new OpenEndLink(intakePipeBeforeThrottle, isLeftEnd: true)
|
||||
{
|
||||
AmbientPressure = 101325.0,
|
||||
Gamma = 1.4
|
||||
};
|
||||
solver.AddOpenEndLink(intakeOpenEnd);
|
||||
|
||||
// ---- Throttle ----
|
||||
throttleOrifice = new OrificeLink(plenumInlet, intakePipeBeforeThrottle, isPipeLeftEnd: false,
|
||||
areaProvider: () => MaxThrottleArea * Math.Clamp(Throttle, 0.001, 1.0))
|
||||
{
|
||||
DischargeCoefficient = 0.2,
|
||||
UseInertance = false
|
||||
};
|
||||
solver.AddOrificeLink(throttleOrifice);
|
||||
|
||||
stepCount = 0;
|
||||
Console.WriteLine("Inline-4 engine test");
|
||||
Console.WriteLine($"Bore {bore * 1000:F0}mm, Stroke {stroke * 1000:F0}mm, CR {compRatio}");
|
||||
Console.WriteLine("Firing order 1-3-4-2, 180° intervals");
|
||||
}
|
||||
|
||||
public override float Process()
|
||||
{
|
||||
crankshaft.Step(dt);
|
||||
|
||||
cyl1.PreStep(dt);
|
||||
cyl2.PreStep(dt);
|
||||
cyl3.PreStep(dt);
|
||||
cyl4.PreStep(dt);
|
||||
|
||||
solver.Step();
|
||||
stepCount++;
|
||||
|
||||
if (stepCount % 10000 == 0)
|
||||
{
|
||||
double rpm = crankshaft.AngularVelocity * 60.0 / (2.0 * Math.PI);
|
||||
Console.WriteLine($"Step {stepCount}, RPM = {rpm:F0}, " +
|
||||
$"cyl1 P = {cyl1.Pressure / 1e5:F2} bar, " +
|
||||
$"plenum P = {intakePlenum.Pressure / 1e5:F2} bar");
|
||||
}
|
||||
|
||||
// Mix all exhaust sounds
|
||||
float exhaustMix = exhaustSoundProcessor.Process(exhaustOpenEnd1)
|
||||
+ exhaustSoundProcessor.Process(exhaustOpenEnd2)
|
||||
+ exhaustSoundProcessor.Process(exhaustOpenEnd3)
|
||||
+ exhaustSoundProcessor.Process(exhaustOpenEnd4);
|
||||
float intakeDry = intakeSoundProcessor.Process(intakeOpenEnd);
|
||||
return reverb.Process(exhaustMix * 0.25f + intakeDry);
|
||||
}
|
||||
|
||||
public override void Draw(RenderWindow target)
|
||||
{
|
||||
float winW = target.GetView().Size.X;
|
||||
float winH = target.GetView().Size.Y;
|
||||
|
||||
float startX = 60f;
|
||||
float spacing = 80f;
|
||||
float intakeY = winH / 2f - 80f;
|
||||
float exhaustY = winH / 2f + 80f;
|
||||
|
||||
// Plenum
|
||||
float plenW = 50f, plenH = 120f;
|
||||
float plenX = startX;
|
||||
float plenTopY = intakeY - plenH / 2f;
|
||||
DrawVolume(target, intakePlenum, plenX, plenTopY, plenW, plenH);
|
||||
|
||||
// Helper arrays just for drawing (no closures)
|
||||
var cyls = new[] { cyl1, cyl2, cyl3, cyl4 };
|
||||
var runners = new[] { runner1, runner2, runner3, runner4 };
|
||||
var exhausts = new[] { exh1, exh2, exh3, exh4 };
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
float cylX = plenX + plenW + 30f + i * spacing;
|
||||
float runnerStartX = plenX + plenW + 5f;
|
||||
float runnerEndX = cylX - 20f;
|
||||
DrawPipe(target, runners[i], intakeY, runnerStartX, runnerEndX);
|
||||
|
||||
float cylTopY = intakeY - 120f;
|
||||
DrawCylinder(target, cyls[i], cylX, cylTopY, 70f, 200f);
|
||||
|
||||
float exhStartX = cylX + 35f;
|
||||
float exhEndX = exhStartX + 100f;
|
||||
DrawPipe(target, exhausts[i], exhaustY, exhStartX, exhEndX);
|
||||
|
||||
var mark = new CircleShape(4f) { FillColor = Color.Magenta };
|
||||
mark.Position = new Vector2f(exhEndX - 4f, exhaustY - 4f);
|
||||
target.Draw(mark);
|
||||
}
|
||||
|
||||
// Throttle symbol
|
||||
var throttleRect = new RectangleShape(new Vector2f(6f, 30f))
|
||||
{
|
||||
FillColor = Color.Yellow,
|
||||
Position = new Vector2f(plenX - 16f, intakeY - 15f)
|
||||
};
|
||||
target.Draw(throttleRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ namespace FluidSim.Tests
|
||||
|
||||
protected const double AmbientPressure = 101325.0;
|
||||
protected const double AmbientTemperature = 300.0;
|
||||
public double Throttle { get; set; } = 0.0;
|
||||
|
||||
// ---------- Color from pressure (volumes) ----------
|
||||
protected Color PressureColor(double pressurePa)
|
||||
|
||||
@@ -37,8 +37,7 @@ namespace FluidSim.Tests
|
||||
private int stepCount;
|
||||
|
||||
// ---------- Throttle control ----------
|
||||
public double Throttle { get; set; } = 0.0;
|
||||
public double MaxThrottleArea { get; set; } = 3 * Units.cm2; // 2 cm²
|
||||
public double MaxThrottleArea { get; set; } = 1 * Units.cm2; // 2 cm²
|
||||
|
||||
public override void Initialize(int sampleRate)
|
||||
{
|
||||
@@ -50,7 +49,7 @@ namespace FluidSim.Tests
|
||||
|
||||
// ---- Crankshaft (external, passed to cylinder) ----
|
||||
crankshaft = new Crankshaft(600);
|
||||
crankshaft.Inertia = 0.1;
|
||||
crankshaft.Inertia = 0.2;
|
||||
crankshaft.FrictionConstant = 2;
|
||||
crankshaft.FrictionViscous = 0.04;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user