refined
This commit is contained in:
@@ -19,15 +19,20 @@ namespace FluidSim.Components
|
||||
public double ConRodLength { get; }
|
||||
public double CompressionRatio { get; }
|
||||
|
||||
// Valve timings
|
||||
// Valve timings (degrees, 0 = TDC compression, 720° full cycle)
|
||||
public double IVO { get; }
|
||||
public double IVC { get; }
|
||||
public double EVO { get; }
|
||||
public double EVC { get; }
|
||||
|
||||
// Valve areas
|
||||
public double MaxIntakeArea { get; set; } = 0.0005;
|
||||
public double MaxExhaustArea { get; set; } = 0.0005;
|
||||
// Valve geometry
|
||||
public double IntakeValveDiameter { get; set; } = 0.030;
|
||||
public double ExhaustValveDiameter { get; set; } = 0.028;
|
||||
public double IntakeValveLift { get; set; } = 0.005;
|
||||
public double ExhaustValveLift { get; set; } = 0.005;
|
||||
|
||||
public double IntakeValveMaxArea => Math.PI * IntakeValveDiameter * IntakeValveLift;
|
||||
public double ExhaustValveMaxArea => Math.PI * ExhaustValveDiameter * ExhaustValveLift;
|
||||
|
||||
// Ignition and combustion
|
||||
public double SparkAdvance { get; set; } = 20.0;
|
||||
@@ -40,6 +45,12 @@ namespace FluidSim.Components
|
||||
public double StoichiometricAFR { get; set; } = 14.7;
|
||||
public double FuelLowerHeatingValue { get; set; } = 44e6;
|
||||
|
||||
// Cycle‑to‑cycle randomness
|
||||
/// <summary>Fractional variation in fuel energy (±). 0.05 = ±5%.</summary>
|
||||
public double EnergyVariationFraction { get; set; } = 0.05;
|
||||
/// <summary>Probability of a misfire (0‑1).</summary>
|
||||
public double MisfireProbability { get; set; } = 0.01;
|
||||
|
||||
// Heat loss
|
||||
public double CylinderWallArea { get; set; } = 0.02;
|
||||
public double HeatTransferCoefficient { get; set; } = 100.0;
|
||||
@@ -64,6 +75,10 @@ namespace FluidSim.Components
|
||||
private bool combustionActive;
|
||||
private bool fuelInjected;
|
||||
|
||||
// per‑cycle randomness
|
||||
private double _energyFactor = 1.0; // applied to FuelLowerHeatingValue this cycle
|
||||
private readonly Random _random = new Random();
|
||||
|
||||
private const double Gamma = 1.4;
|
||||
private const double GasConstant = 287.0;
|
||||
|
||||
@@ -95,6 +110,7 @@ namespace FluidSim.Components
|
||||
_ports = new[] { IntakePort, ExhaustPort };
|
||||
}
|
||||
|
||||
// Derived volumes
|
||||
private double SweptVolume => Math.PI * 0.25 * Bore * Bore * Stroke;
|
||||
private double clearanceVolume => SweptVolume / (CompressionRatio - 1.0);
|
||||
private double CrankRadius => Stroke / 2.0;
|
||||
@@ -113,24 +129,40 @@ namespace FluidSim.Components
|
||||
return clearanceVolume + area * x;
|
||||
}
|
||||
|
||||
public double IntakeValveArea => ValveArea(CrankDeg, IVO, IVC, MaxIntakeArea);
|
||||
public double ExhaustValveArea => ValveArea(CrankDeg, EVO, EVC, MaxExhaustArea);
|
||||
|
||||
private double ValveArea(double thetaDeg, double opens, double closes, double maxArea)
|
||||
private double ValveLift(double thetaDeg, double opens, double closes, double peakLift)
|
||||
{
|
||||
double deg = thetaDeg % 720.0;
|
||||
if (deg < 0) deg += 720.0;
|
||||
if (deg >= opens && deg <= closes)
|
||||
|
||||
double duration = closes - opens;
|
||||
if (duration <= 0) return 0.0;
|
||||
|
||||
double rampDur = duration * 0.25;
|
||||
double holdDur = duration - 2.0 * rampDur;
|
||||
|
||||
if (deg >= opens && deg < opens + rampDur)
|
||||
{
|
||||
double half = (closes - opens) * 0.5;
|
||||
double mid = opens + half;
|
||||
double frac = 1.0 - Math.Abs(deg - mid) / half;
|
||||
frac = Math.Clamp(frac, 0.0, 1.0);
|
||||
return maxArea * frac;
|
||||
double t = (deg - opens) / rampDur;
|
||||
return peakLift * t * t * (3.0 - 2.0 * t);
|
||||
}
|
||||
else if (deg >= opens + rampDur && deg < opens + rampDur + holdDur)
|
||||
{
|
||||
return peakLift;
|
||||
}
|
||||
else if (deg >= opens + rampDur + holdDur && deg <= closes)
|
||||
{
|
||||
double t = (deg - (opens + rampDur + holdDur)) / rampDur;
|
||||
return peakLift * (1.0 - t) * (1.0 - t) * (1.0 + 2.0 * t);
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
public double IntakeValveArea =>
|
||||
Math.PI * IntakeValveDiameter * ValveLift(CrankDeg, IVO, IVC, IntakeValveLift);
|
||||
|
||||
public double ExhaustValveArea =>
|
||||
Math.PI * ExhaustValveDiameter * ValveLift(CrankDeg, EVO, EVC, ExhaustValveLift);
|
||||
|
||||
private double Wiebe(double angleSinceSpark)
|
||||
{
|
||||
if (angleSinceSpark < WiebeStart) return 0.0;
|
||||
@@ -147,8 +179,8 @@ namespace FluidSim.Components
|
||||
|
||||
double dV = cylinderVolume - prevVolume;
|
||||
|
||||
// ---- Piston torque ----
|
||||
double pRel = Pressure - 101325.0; // relative to ambient
|
||||
// Piston torque
|
||||
double pRel = Pressure - 101325.0;
|
||||
double sinTh = Math.Sin(crankAngleRad);
|
||||
double cosTh = Math.Cos(crankAngleRad);
|
||||
double term = Math.Sqrt(1.0 - Obliquity * Obliquity * sinTh * sinTh);
|
||||
@@ -157,13 +189,12 @@ namespace FluidSim.Components
|
||||
double torque = pRel * pistonArea * dxdtheta;
|
||||
Crankshaft.AddTorque(torque);
|
||||
|
||||
// Volume work (done BY gas, positive when expanding)
|
||||
cylinderEnergy -= Pressure * dV;
|
||||
|
||||
double prevDeg = Crankshaft.PreviousAngle * 180.0 / Math.PI % 720.0;
|
||||
double currDeg = crankAngleRad * 180.0 / Math.PI % 720.0;
|
||||
|
||||
// Intake closing: capture trapped air mass (air only!)
|
||||
// ----- Intake closing: capture trapped air mass and compute fuel -----
|
||||
if (prevDeg >= IVO && prevDeg < IVC && currDeg >= IVC)
|
||||
{
|
||||
trappedAirMass = _airMass;
|
||||
@@ -171,23 +202,39 @@ namespace FluidSim.Components
|
||||
fuelInjected = true;
|
||||
}
|
||||
|
||||
// Spark
|
||||
// ----- Spark ignition (once per cycle, with misfire chance) -----
|
||||
double sparkAngle = 0.0 - SparkAdvance;
|
||||
if (sparkAngle < 0) sparkAngle += 720.0;
|
||||
|
||||
bool crossedSpark = (prevDeg < sparkAngle && currDeg >= sparkAngle) ||
|
||||
(prevDeg > sparkAngle + 360.0 && currDeg < sparkAngle);
|
||||
if (crossedSpark && !combustionActive && fuelInjected)
|
||||
{
|
||||
combustionActive = true;
|
||||
burnFraction = 0.0;
|
||||
// Decide misfire
|
||||
bool misfire = _random.NextDouble() < MisfireProbability;
|
||||
if (misfire)
|
||||
{
|
||||
combustionActive = false; // no combustion this cycle
|
||||
// fuel is not burned – will remain in cylinder and eventually exit as unburned mixture
|
||||
}
|
||||
else
|
||||
{
|
||||
combustionActive = true;
|
||||
burnFraction = 0.0;
|
||||
|
||||
// Energy variation factor for this cycle
|
||||
double range = EnergyVariationFraction;
|
||||
_energyFactor = 1.0 + range * (2.0 * _random.NextDouble() - 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
// Combustion progress
|
||||
// ----- Combustion progress -----
|
||||
if (combustionActive)
|
||||
{
|
||||
double angleSinceSpark = currDeg - sparkAngle;
|
||||
if (angleSinceSpark < 0) angleSinceSpark += 720.0;
|
||||
double newFraction = Wiebe(angleSinceSpark);
|
||||
|
||||
if (newFraction >= 1.0 || angleSinceSpark > (WiebeDuration + WiebeStart + SparkAdvance))
|
||||
{
|
||||
newFraction = 1.0;
|
||||
@@ -201,14 +248,14 @@ namespace FluidSim.Components
|
||||
double dFraction = newFraction - burnFraction;
|
||||
if (dFraction > 0)
|
||||
{
|
||||
double dQ = fuelMass * FuelLowerHeatingValue * dFraction;
|
||||
double dQ = fuelMass * FuelLowerHeatingValue * _energyFactor * dFraction;
|
||||
cylinderEnergy += dQ;
|
||||
_exhaustMass += fuelMass * dFraction; // burning fuel adds to exhaust
|
||||
_exhaustMass += fuelMass * dFraction;
|
||||
burnFraction = newFraction;
|
||||
}
|
||||
}
|
||||
|
||||
// Heat loss
|
||||
// ----- Heat loss to cylinder walls -----
|
||||
double dQ_loss = HeatTransferCoefficient * CylinderWallArea *
|
||||
(Temperature - AmbientTemperature) * dt;
|
||||
cylinderEnergy -= dQ_loss;
|
||||
@@ -250,7 +297,6 @@ namespace FluidSim.Components
|
||||
|
||||
double V = Math.Max(cylinderVolume, 1e-12);
|
||||
|
||||
// Safety clamps
|
||||
double currentP = (Gamma - 1.0) * cylinderEnergy / V;
|
||||
if (currentP > MaxPressurePa)
|
||||
cylinderEnergy = MaxPressurePa * V / (Gamma - 1.0);
|
||||
|
||||
@@ -16,8 +16,8 @@ namespace FluidSim.Components
|
||||
public Port PortA { get; }
|
||||
public Port PortB { get; }
|
||||
public double Area { get; }
|
||||
public double DampingMultiplier { get; set; } = 1.0;
|
||||
public double EnergyRelaxationRate { get; set; } = 0.0; // 1/s
|
||||
public double DampingMultiplier { get; set; } = 10.0;
|
||||
public double EnergyRelaxationRate { get; set; } = 5.0; // 1/s
|
||||
|
||||
private double _ambientPressure = 101325.0;
|
||||
public double AmbientPressure
|
||||
|
||||
Reference in New Issue
Block a user