Files
FluidSim/Scenarios/TestScenario.cs

220 lines
9.2 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 System;
using SFML.Graphics;
using SFML.System;
using FluidSim.Components;
using FluidSim.Core;
namespace FluidSim.Tests
{
public class TestScenario : Scenario
{
// Engine
private Cylinder cylinder;
// Intake side
private Pipe1D intakePipeBeforeThrottle; // pipe from ambient to plenum
private Volume0D intakePlenum; // plenum (100 mL)
private Pipe1D intakeRunner; // pipe from plenum to cylinder
// Exhaust side
private Pipe1D exhaustPipe;
// Links
private OpenEndLink intakeOpenEnd; // ambient → left end of first pipe
private OrificeLink throttleOrifice; // first pipe right end → plenum inlet (variable area)
private OrificeLink plenumToRunner; // plenum outlet → runner left end (fixed area)
private OrificeLink intakeValve; // runner right end → cylinder intake port
private OrificeLink exhaustValve;
private OpenEndLink exhaustOpenEnd;
private Solver solver;
private SoundProcessor soundProcessor;
private double dt;
private int stepCount;
public double ThrottleArea { get; set; } = 0.0; // controlled externally
public override void Initialize(int sampleRate)
{
dt = 1.0 / sampleRate;
soundProcessor = new SoundProcessor(sampleRate, 1) { Gain = 1f };
solver = new Solver();
solver.SetTimeStep(dt);
solver.CflTarget = 0.9;
// ---- Cylinder (no valve overlap to avoid backflow) ----
double bore = 0.056, stroke = 0.050, conRod = 0.110, compRatio = 10.0;
double ivo = 370.0, ivc = 580.0, evo = 120.0, evc = 350.0;
cylinder = new Cylinder(bore, stroke, conRod, compRatio, ivo, ivc, evo, evc, 1000)
{
MaxIntakeArea = 0.00037,
MaxExhaustArea = 0.00037,
};
solver.AddComponent(cylinder);
double pipeArea = 0.00037; // 3.7 cm²
// ---- Pipes ----
intakePipeBeforeThrottle = new Pipe1D(0.15, pipeArea, 5); // short pipe before throttle
intakeRunner = new Pipe1D(0.10, pipeArea, 5); // runner after plenum
exhaustPipe = new Pipe1D(1.00, pipeArea, 80);
solver.AddComponent(intakePipeBeforeThrottle);
solver.AddComponent(intakeRunner);
solver.AddComponent(exhaustPipe);
// ---- Plenum (100 mL) ----
intakePlenum = new Volume0D(0.0001, 101325.0, 300.0); // 0.0001 m³
var plenumInlet = intakePlenum.CreatePort(); // from throttle
var plenumOutlet = intakePlenum.CreatePort(); // to runner
solver.AddComponent(intakePlenum);
// ---- Intake open end (ambient → left end of first pipe) ----
intakeOpenEnd = new OpenEndLink(intakePipeBeforeThrottle, isLeftEnd: true)
{
AmbientPressure = 101325.0,
Gamma = 1.4
};
solver.AddOpenEndLink(intakeOpenEnd);
// ---- Throttle orifice (first pipe right end → plenum inlet) ----
throttleOrifice = new OrificeLink(plenumInlet, intakePipeBeforeThrottle, isPipeLeftEnd: false,
areaProvider: () => ThrottleArea)
{
DischargeCoefficient = 0.1, // realistic throttle Cd
UseInertance = false
};
solver.AddOrificeLink(throttleOrifice);
// ---- Plenum → runner (fixed area = pipe area) ----
plenumToRunner = new OrificeLink(plenumOutlet, intakeRunner, isPipeLeftEnd: true,
areaProvider: () => pipeArea)
{
DischargeCoefficient = 1.0,
UseInertance = false
};
solver.AddOrificeLink(plenumToRunner);
// ---- Intake valve (runner right end → cylinder intake port) ----
intakeValve = new OrificeLink(cylinder.IntakePort, intakeRunner, isPipeLeftEnd: false,
areaProvider: () => cylinder.IntakeValveArea)
{
DischargeCoefficient = 1.0,
UseInertance = false
};
solver.AddOrificeLink(intakeValve);
// ---- Exhaust valve ----
exhaustValve = new OrificeLink(cylinder.ExhaustPort, exhaustPipe, isPipeLeftEnd: true,
areaProvider: () => cylinder.ExhaustValveArea)
{
DischargeCoefficient = 1.0,
UseInertance = false
};
solver.AddOrificeLink(exhaustValve);
// ---- Exhaust open end ----
exhaustOpenEnd = new OpenEndLink(exhaustPipe, isLeftEnd: false)
{
AmbientPressure = 101325.0,
Gamma = 1.4
};
solver.AddOpenEndLink(exhaustOpenEnd);
stepCount = 0;
Console.WriteLine("4Stroke engine test (plenum + two pipes)");
Console.WriteLine($"Bore {bore * 1000:F0}mm, Stroke {stroke * 1000:F0}mm, CR {compRatio}");
Console.WriteLine($"IVO {ivo}°, IVC {ivc}°, EVO {evo}°, EVC {evc}° (no overlap)");
}
public override float Process()
{
// 1. Advance crankshaft & prestep
cylinder.Crankshaft.Step(dt);
cylinder.PreStep(dt);
// 2. Run solver
solver.Step();
stepCount++;
// 3. Log every 200 steps
if (stepCount % 200 == 0)
{
double crankDeg = cylinder.Crankshaft.CrankAngle * 180.0 / Math.PI % 720.0;
double cylP = cylinder.Pressure / 1e5;
double cylT = cylinder.Temperature;
double cylMass = cylinder.Mass * 1e6;
double mdotI = intakeValve.LastMassFlowRate;
double mdotE = exhaustValve.LastMassFlowRate;
double pipeR = exhaustPipe.GetCellPressure(exhaustPipe.CellCount - 1) / 1e5;
double plenumP = intakePlenum.Pressure / 1e5;
Console.WriteLine($"Step {stepCount}: Angle={crankDeg:F1}°, " +
$"CylP={cylP:F2} bar, T={cylT:F0} K, mass={cylMass:F1} mg, " +
$"mdotI={mdotI:E4} kg/s, mdotE={mdotE:E4} kg/s, PipeR={pipeR:F2} bar");
Console.WriteLine($"Throttle area = {ThrottleArea * 1e6:F2} mm², Plenum P = {plenumP:F3} bar");
}
return soundProcessor.Process(exhaustOpenEnd);
}
public override void Draw(RenderWindow target)
{
float winW = target.GetView().Size.X;
float winH = target.GetView().Size.Y;
// Fixed vertical centres for intake and exhaust
float intakeY = winH / 2f - 40f;
float exhaustY = winH / 2f + 80f;
// ---- 1. Open end (ambient air source) ----
float openEndX = 40f;
var openEndMark = new CircleShape(5f) { FillColor = Color.Cyan };
openEndMark.Position = new Vector2f(openEndX - 5f, intakeY - 5f);
target.Draw(openEndMark);
// ---- 2. First intake pipe (ambient → throttle) ----
float pipe1StartX = openEndX;
float pipe1EndX = pipe1StartX + 120f;
DrawPipe(target, intakePipeBeforeThrottle, intakeY, pipe1StartX, pipe1EndX);
// ---- 3. Throttle (symbolic restriction) ----
float throttleX = pipe1EndX + 5f;
var throttleRect = new RectangleShape(new Vector2f(8f, 30f))
{
FillColor = Color.Yellow,
Position = new Vector2f(throttleX, intakeY - 15f)
};
target.Draw(throttleRect);
// ---- 4. Plenum ----
float plenW = 60f, plenH = 80f;
float plenLeftX = throttleX + 10f;
float plenCenterX = plenLeftX + plenW / 2f;
float plenTopY = intakeY - plenH / 2f;
DrawVolume(target, intakePlenum, plenCenterX, plenTopY, plenW, plenH);
// ---- 5. Runner pipe (plenum → cylinder) ----
float runnerStartX = plenLeftX + plenW + 5f;
float runnerEndX = runnerStartX + 100f;
DrawPipe(target, intakeRunner, intakeY, runnerStartX, runnerEndX);
// ---- 6. Cylinder ----
float cylCX = runnerEndX + 50f; // center X
float cylTopY = intakeY - 120f; // top of cylinder (so it sits above the pipe)
float cylW = 80f, cylMaxH = 240f;
DrawCylinder(target, cylinder, cylCX, cylTopY, cylW, cylMaxH);
// ---- 7. Exhaust pipe (cylinder → open end) ----
float exhStartX = cylCX + cylW / 2f + 20f;
float exhEndX = winW - 60f;
DrawPipe(target, exhaustPipe, exhaustY, exhStartX, exhEndX);
// Exhaust open end marker
var exhOpenEndMark = new CircleShape(5f) { FillColor = Color.Magenta };
exhOpenEndMark.Position = new Vector2f(exhEndX - 5f, exhaustY - 5f);
target.Draw(exhOpenEndMark);
}
}
}