From aba9b7653050b339f4cd575dd09733561748dab3 Mon Sep 17 00:00:00 2001 From: max Date: Tue, 9 Jun 2026 18:05:39 +0200 Subject: [PATCH] config tuning --- Core/BoundarySystem.cs | 22 ++- Scenarios/SingleCylScenario.cs | 238 +++++++++++++-------------------- 2 files changed, 105 insertions(+), 155 deletions(-) diff --git a/Core/BoundarySystem.cs b/Core/BoundarySystem.cs index de21abd..aa30a70 100644 --- a/Core/BoundarySystem.cs +++ b/Core/BoundarySystem.cs @@ -25,8 +25,7 @@ namespace FluidSim.Core public float EffectiveLength; public float CurrentMdot; // kg/s, positive = volume → pipe - // --- Loss coefficient (linear resistance) – inertance only --- - // If 0 when UseInertance is true, a stable default is auto‑computed at runtime. + // --- Loss coefficient (linear resistance) --- public float LossCoefficient; // N·s/m⁵ or kg/(m⁴·s) } @@ -58,10 +57,9 @@ namespace FluidSim.Core public int OpenEndCount { get; private set; } // ---------- Add orifice (no inertance) ---------- - // Simple isentropic nozzle – no built‑in loss. For dissipation use pipe damping - // or the inertance model if you need a damped resonator. public void AddOrifice(Port volumePort, int pipeIndex, bool isLeftEnd, - int areaIndex, float dischargeCoeff = 1f) + int areaIndex, float dischargeCoeff = 1f, + float lossCoefficient = 0f) { _orifices[OrificeCount] = new OrificeDesc { @@ -73,24 +71,22 @@ namespace FluidSim.Core UseInertance = false, EffectiveLength = 0f, CurrentMdot = 0f, - LossCoefficient = 0f + LossCoefficient = lossCoefficient }; OrificeCount++; } // ---------- Add orifice with inertance ---------- - // effectiveLength – length of the inertial slug (m), typically the physical neck length. - // lossCoefficient – linear resistance (N·s/m⁵). If 0 (or omitted) an automatic stable - // value will be computed from the pipe's characteristic impedance. public void AddOrificeWithInertance(Port volumePort, int pipeIndex, bool isLeftEnd, int areaIndex, float dischargeCoeff, float effectiveLength, float lossCoefficient = 0f) { - AddOrifice(volumePort, pipeIndex, isLeftEnd, areaIndex, dischargeCoeff); + // Reuse the base AddOrifice and then override fields + AddOrifice(volumePort, pipeIndex, isLeftEnd, areaIndex, dischargeCoeff, lossCoefficient); ref var d = ref _orifices[OrificeCount - 1]; d.UseInertance = true; d.EffectiveLength = effectiveLength; - d.LossCoefficient = lossCoefficient; + d.LossCoefficient = lossCoefficient; // store the linear resistance } public void AddOpenEnd(int pipeIndex, bool isLeftEnd, @@ -150,7 +146,7 @@ namespace FluidSim.Core ? _pipeSystem.GetInteriorAirFractionLeft(d.PipeIndex) : _pipeSystem.GetInteriorAirFractionRight(d.PipeIndex); - // ---- Handle closed orifice as a wall ---- + // ---- Handle closed orifice (area ≈ 0) as a wall ---- if (area < 1e-12f || d.VolumePort == null) { var (rInt, uInt, pInt) = d.IsLeftEnd @@ -188,10 +184,10 @@ namespace FluidSim.Core if (d.UseInertance) { - // ---- Inertance ODE with (possibly automatic) linear loss ---- float rhoUp = d.CurrentMdot >= 0 ? volRho : pipeRho; float inertance = rhoUp * d.EffectiveLength / MathF.Max(area, 1e-12f); float dp = volP - pipeP; + float Rlin = d.LossCoefficient; float dmdot_dt = (dp - Rlin * d.CurrentMdot) / MathF.Max(inertance, 1e-12f); float mdotNew = d.CurrentMdot + dmdot_dt * dt; diff --git a/Scenarios/SingleCylScenario.cs b/Scenarios/SingleCylScenario.cs index baf8aed..7046639 100644 --- a/Scenarios/SingleCylScenario.cs +++ b/Scenarios/SingleCylScenario.cs @@ -1,6 +1,7 @@ using FluidSim.Components; using FluidSim.Core; using FluidSim.Interfaces; +using FluidSim.Utils; using SFML.Graphics; using SFML.System; using System; @@ -9,115 +10,75 @@ 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; + private Volume0D exhaustCollector; + private Port colIn, colOut; - // Orifice / open‑end indices - private int throttleAreaIdx, plenumRunnerIdx, intakeValveIdx, exhaustValveIdx; - private int intakeOpenIdx, exhaustOpenIdx; + private int throttleAreaIdx, plenumRunnerAreaIdx, intakeValveIdx, exhaustValveIdx; private float[] orificeAreas; + private int intakeOpenIdx, exhaustOpenIdx; - // 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; + // Use a private field for the maximum throttle area, avoiding any base‑class conflicts + private float _maxThrottleArea; - // 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° four‑stroke) - // 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; + // pipe area for open end calculations + private float pipeArea; 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; + // Maximum throttle area – independent of base class + _maxThrottleArea = (float)Units.AreaFromDiameter(3 * Units.cm); // 1 cm² - // ---------- Cylinder ---------- - cylinder = new Cylinder(Bore, Stroke, ConRod, CompressionRatio, - IVO, IVC, EVO, EVC, crankshaft) + // ---- Crankshaft ---- + crankshaft = new Crankshaft(2000); + crankshaft.Inertia = 0.01f; + crankshaft.FrictionConstant = 2f; + crankshaft.FrictionViscous = 0.0f; + + // ---- Cylinder ---- + float bore = 0.056f, stroke = 0.057f, conRod = 0.110f, compRatio = 11f; + float ivo = 350f, ivc = 580f, evo = 120f, evc = 370f; + cylinder = new Cylinder(bore, stroke, conRod, compRatio, + ivo, ivc, evo, evc, crankshaft) { - IntakeValveDiameter = IntakeValveDiam, - ExhaustValveDiameter = ExhaustValveDiam, - IntakeValveLift = ValveLift, - ExhaustValveLift = ValveLift, - SparkAdvance = SparkAdv, - EnergyVariationFraction = 0.03f, // small cycle‑to‑cycle variation - MisfireProbability = 0.0f + IntakeValveDiameter = 0.03f, + IntakeValveLift = 0.005f, + ExhaustValveDiameter = 0.028f, + ExhaustValveLift = 0.005f }; - // ---------- 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; - + // ---- Pipe system ---- + int[] pipeStart = { 0, 10, 20 }; + int[] pipeEnd = { 10, 20, 70 }; + int totalCells = pipeEnd[^1]; // automatically 70, stays in sync + float[] area = new float[totalCells]; + float[] dx = new float[totalCells]; + float pipeDiameter = 0.02f; // 2 cm + pipeArea = MathF.PI * 0.25f * pipeDiameter * pipeDiameter; + float areaVal = pipeArea; + float intakeLenBefore = 0.2f, intakeLenRunner = 0.2f, exhaustLen = 0.4f; 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; + area[i] = areaVal; + if (i < 10) dx[i] = intakeLenBefore / 10f; + else if (i < 20) dx[i] = intakeLenRunner / 10f; + else dx[i] = exhaustLen / 50f; } pipeSystem = new PipeSystem(totalCells, pipeStart, pipeEnd, area, dx, @@ -130,45 +91,49 @@ namespace FluidSim.Tests intakePlenum = new Volume0D(100e-6f, 101325f, 300f); // 100 mL plenumInlet = intakePlenum.CreatePort(); plenumOutlet = intakePlenum.CreatePort(); + exhaustCollector = new Volume0D(10e-6f, 101325f, 800f); // 10 mL (unused but present) + colIn = exhaustCollector.CreatePort(); + colOut = exhaustCollector.CreatePort(); - // ---------- Boundary system ---------- + // ---- Boundary system ---- boundaries = new BoundarySystem(pipeSystem, maxOrifices: 4, maxOpenEnds: 2); - throttleAreaIdx = 0; - plenumRunnerIdx = 1; - intakeValveIdx = 2; - exhaustValveIdx = 3; + throttleAreaIdx = 0; + plenumRunnerAreaIdx = 1; + intakeValveIdx = 2; + exhaustValveIdx = 3; - // Open ends - boundaries.AddOpenEnd(pipeIndex: 0, isLeftEnd: true, 101325f, PipeArea); + // Intake open end (pipe0 left) + boundaries.AddOpenEnd(pipeIndex: 0, isLeftEnd: true, 101325f, pipeArea); intakeOpenIdx = 0; - boundaries.AddOpenEnd(pipeIndex: 2, isLeftEnd: false, 101325f, PipeArea); + + // Throttle orifice (plenum inlet to pipe0 right) + boundaries.AddOrifice(plenumInlet, pipeIndex: 0, isLeftEnd: false, throttleAreaIdx, 0.2f); + + // Plenum to runner (plenum outlet to pipe1 left) + boundaries.AddOrifice(plenumOutlet, pipeIndex: 1, isLeftEnd: true, plenumRunnerAreaIdx, 1f); + + // Intake valve (cylinder intake to pipe1 right) + boundaries.AddOrifice(cylinder.IntakePort, pipeIndex: 1, isLeftEnd: false, intakeValveIdx, 1f); + + // Exhaust valve (cylinder exhaust to pipe2 left) + boundaries.AddOrifice(cylinder.ExhaustPort, pipeIndex: 2, isLeftEnd: true, exhaustValveIdx, 1f); + + // Exhaust open end (pipe2 right) + 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 full‑bore + orificeAreas[plenumRunnerAreaIdx] = areaVal; // fixed plenum->runner area - // ---------- Solver ---------- - solver = new Solver { SubStepCount = 5, EnableProfiling = false }; + // ---- Solver ---- + solver = new Solver { SubStepCount = 4, EnableProfiling = false }; solver.SetTimeStep(dt); solver.SetPipeSystem(pipeSystem); solver.SetBoundarySystem(boundaries); solver.AddComponent(cylinder); solver.AddComponent(intakePlenum); + solver.AddComponent(exhaustCollector); // ---- Sound ---- exhaustSound = new SoundProcessor(sampleRate, 1f) { Gain = 20f }; @@ -176,37 +141,35 @@ namespace FluidSim.Tests reverb = new OutdoorExhaustReverb(sampleRate); stepCount = 0; - Console.WriteLine("Single‑cylinder engine (YX140) ready."); + Console.WriteLine("TestScenario ready."); } public override float Process() { - // ---- Crank and cylinder pre‑step ---- crankshaft.Step((float)dt); cylinder.PreStep((float)dt); - // ---- Update variable areas ---- - float throttledArea = MaxThrottleArea * Math.Clamp(Throttle, 0.0001f, 1.0f); + // Update variable orifice areas – use the private _maxThrottleArea + float throttledArea = _maxThrottleArea * Math.Clamp(Throttle, 0.0001f, 1f); orificeAreas[throttleAreaIdx] = throttledArea; - orificeAreas[intakeValveIdx] = cylinder.IntakeValveArea; + orificeAreas[intakeValveIdx] = cylinder.IntakeValveArea; orificeAreas[exhaustValveIdx] = cylinder.ExhaustValveArea; boundaries.SetOrificeAreas(orificeAreas); - // ---- Fluids step ---- solver.Step(); stepCount++; - // ---- Sound ---- + // Retrieve open‑end mass flows for sound synthesis float exhaustFlow = boundaries.GetOpenEndMassFlow(exhaustOpenIdx); - float intakeFlow = boundaries.GetOpenEndMassFlow(intakeOpenIdx); + float intakeFlow = boundaries.GetOpenEndMassFlow(intakeOpenIdx); float exhaustDry = exhaustSound.Process(exhaustFlow); - float intakeDry = intakeSound.Process(intakeFlow); + float intakeDry = intakeSound.Process(intakeFlow); - if (stepCount % 2000 == 0) + if (stepCount % 1000 == 0) { float rpm = crankshaft.AngularVelocity * 60f / (2f * MathF.PI); - float crankDeg = crankshaft.CrankAngle; // public property on Cylinder + float crankDeg = crankshaft.CrankAngle; // degrees (0–720) 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}"); @@ -240,53 +203,44 @@ namespace FluidSim.Tests float intakeY = winH / 2f - 40f; float exhaustY = winH / 2f + 80f; - float leftX = 40f; + float openEndX = 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); + // Intake pipe before throttle (pipe 0) + float pipe1StartX = openEndX; + float pipe1EndX = pipe1StartX + 120f; + DrawPipe(target, pipeSystem, 0, intakeY, pipe1StartX, pipe1EndX); // Throttle symbol - float thrX = p0EndX + 5f; - var thr = new RectangleShape(new Vector2f(8f, 30f)) + float throttleX = pipe1EndX + 5f; + var throttleRect = new RectangleShape(new Vector2f(8f, 30f)) { FillColor = Color.Yellow, - Position = new Vector2f(thrX, intakeY - 15f) + Position = new Vector2f(throttleX, intakeY - 15f) }; - target.Draw(thr); + target.Draw(throttleRect); - // Plenum volume - float plenW = 60f, plenH = 50f; - float plenLeftX = thrX + 12f; + // 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); - // Pipe 1 – runner - float rStartX = plenLeftX + plenW + 10f; - float rEndX = rStartX + 100f; - DrawPipe(target, pipeSystem, 1, intakeY, rStartX, rEndX); + // Runner pipe (pipe 1) + float runnerStartX = plenLeftX + plenW + 5f; + float runnerEndX = runnerStartX + 100f; + DrawPipe(target, pipeSystem, 1, intakeY, runnerStartX, runnerEndX); // Cylinder - float cylCX = rEndX + 50f; + float cylCX = runnerEndX + 50f; float cylTopY = intakeY - 120f; float cylW = 80f, cylMaxH = 240f; DrawCylinder(target, cylinder, cylCX, cylTopY, cylW, cylMaxH); - // Pipe 2 – exhaust + // Exhaust pipe (pipe 2) 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); } } } \ No newline at end of file