Engine working

This commit is contained in:
max
2026-05-07 21:48:37 +02:00
parent 92d84eacfe
commit b3230844b7
14 changed files with 441 additions and 486 deletions

View File

@@ -37,7 +37,7 @@ namespace FluidSim.Components
Port.Density = Density;
Port.Temperature = Temperature;
Port.SpecificEnthalpy = SpecificEnthalpy;
// MassFlowRate is set by the solver connector
Port.AirFraction = 1.0;
}
}
}

View File

@@ -6,7 +6,6 @@ namespace FluidSim.Components
{
public class Cylinder : IComponent
{
// Public ports
public Port IntakePort { get; }
public Port ExhaustPort { get; }
public Crankshaft Crankshaft { get; }
@@ -20,7 +19,7 @@ namespace FluidSim.Components
public double ConRodLength { get; }
public double CompressionRatio { get; }
// Valve timings (degrees, 0 = TDC compression, 720° full cycle)
// Valve timings
public double IVO { get; }
public double IVC { get; }
public double EVO { get; }
@@ -31,51 +30,48 @@ namespace FluidSim.Components
public double MaxExhaustArea { get; set; } = 0.0005;
// Ignition and combustion
public double SparkAdvance { get; set; } = 20.0; // °BTDC
public double SparkAdvance { get; set; } = 20.0;
public double WiebeA { get; set; } = 5.0;
public double WiebeM { get; set; } = 2.0;
public double WiebeDuration { get; set; } = 60.0; // degrees
public double WiebeStart { get; set; } = 5.0; // degrees after spark
public double WiebeDuration { get; set; } = 60.0;
public double WiebeStart { get; set; } = 5.0;
// Fuel
public double StoichiometricAFR { get; set; } = 14.7;
public double FuelLowerHeatingValue { get; set; } = 44e6; // J/kg
public double FuelLowerHeatingValue { get; set; } = 44e6;
// Heat loss
public double CylinderWallArea { get; set; } = 0.02; // m²
public double HeatTransferCoefficient { get; set; } = 100.0; // W/(m²·K)
public double AmbientTemperature { get; set; } = 300.0; // K
public double CylinderWallArea { get; set; } = 0.02;
public double HeatTransferCoefficient { get; set; } = 100.0;
public double AmbientTemperature { get; set; } = 300.0;
// State (public for drawing)
// State
public double Volume => cylinderVolume;
public double Pressure => (Gamma - 1.0) * cylinderEnergy / Math.Max(cylinderVolume, 1e-12);
public double Temperature => Pressure / Math.Max(Density * GasConstant, 1e-12);
public double Density => cylinderMass / Math.Max(cylinderVolume, 1e-12);
public double Mass => cylinderMass;
public double Density => Mass / Math.Max(cylinderVolume, 1e-12);
public double Mass => _airMass + _exhaustMass;
public double AirFraction => _airMass / Math.Max(Mass, 1e-12);
public double PistonFraction => (cylinderVolume - clearanceVolume) / SweptVolume;
private double cylinderVolume;
private double cylinderMass;
private double cylinderEnergy;
private double _airMass;
private double _exhaustMass;
private double trappedAirMass;
private double fuelMass;
private double burnFraction; // 01
private double burnFraction;
private bool combustionActive;
private bool fuelInjected;
// --- Debounce flag: allows combustion only below a certain temperature ---
private bool _canCombust = true;
private const double CombustionEnableTemperature = 800.0; // K must cool below this to rearm
private const double Gamma = 1.4;
private const double GasConstant = 287.0;
// Absolute safety limits
private const double MaxPressurePa = 200e5; // 200 bar
private const double MaxTemperatureK = 3500.0; // 3500 K
private const double MaxPressurePa = 200e5;
private const double MaxTemperatureK = 3500.0;
public Cylinder(double bore, double stroke, double conRodLength, double compressionRatio,
double ivo, double ivc, double evo, double evc, double initialRPM = 1000)
double ivo, double ivc, double evo, double evc, Crankshaft crankshaft)
{
Bore = bore;
Stroke = stroke;
@@ -86,10 +82,12 @@ namespace FluidSim.Components
EVO = evo;
EVC = evc;
Crankshaft = new Crankshaft(initialRPM);
Crankshaft = crankshaft ?? throw new ArgumentNullException(nameof(crankshaft));
cylinderVolume = clearanceVolume;
cylinderMass = 1.225 * clearanceVolume;
double initRho = 1.225;
_airMass = initRho * clearanceVolume;
_exhaustMass = 0.0;
cylinderEnergy = 101325.0 * clearanceVolume / (Gamma - 1.0);
IntakePort = new Port { Owner = this };
@@ -97,13 +95,10 @@ 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;
private double Obliquity => CrankRadius / ConRodLength;
// Crank angle in degrees (0720)
private double CrankDeg => (Crankshaft.CrankAngle % (4.0 * Math.PI)) * 180.0 / Math.PI % 720.0;
public double ComputeVolume(double thetaRad)
@@ -125,7 +120,6 @@ namespace FluidSim.Components
{
double deg = thetaDeg % 720.0;
if (deg < 0) deg += 720.0;
if (deg >= opens && deg <= closes)
{
double half = (closes - opens) * 0.5;
@@ -151,45 +145,57 @@ namespace FluidSim.Components
double crankAngleRad = Crankshaft.CrankAngle;
cylinderVolume = ComputeVolume(crankAngleRad);
// Volume work (done BY gas, positive when expanding)
double dV = cylinderVolume - prevVolume;
// ---- Piston torque ----
double pRel = Pressure - 101325.0; // relative to ambient
double sinTh = Math.Sin(crankAngleRad);
double cosTh = Math.Cos(crankAngleRad);
double term = Math.Sqrt(1.0 - Obliquity * Obliquity * sinTh * sinTh);
double dxdtheta = CrankRadius * sinTh * (1.0 + Obliquity * cosTh / term);
double pistonArea = Math.PI * 0.25 * Bore * Bore;
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 and compute fuel -----
// Intake closing: capture trapped air mass (air only!)
if (prevDeg >= IVO && prevDeg < IVC && currDeg >= IVC)
{
trappedAirMass = cylinderMass;
trappedAirMass = _airMass;
fuelMass = trappedAirMass / StoichiometricAFR;
fuelInjected = true;
}
// ----- Spark ignition (once per cycle, only if canCombust) -----
// Spark
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 && _canCombust)
if (crossedSpark && !combustionActive && fuelInjected)
{
combustionActive = true;
burnFraction = 0.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;
combustionActive = false;
_canCombust = false; // require cooldown before next ignition
// All gas becomes exhaust
double totalMass = _airMass + _exhaustMass;
_airMass = 0.0;
_exhaustMass = totalMass;
}
double dFraction = newFraction - burnFraction;
@@ -197,18 +203,12 @@ namespace FluidSim.Components
{
double dQ = fuelMass * FuelLowerHeatingValue * dFraction;
cylinderEnergy += dQ;
cylinderMass += fuelMass * dFraction;
_exhaustMass += fuelMass * dFraction; // burning fuel adds to exhaust
burnFraction = newFraction;
}
}
// ----- Rearm combustion if temperature has dropped low enough -----
if (!combustionActive && !_canCombust && Temperature < CombustionEnableTemperature)
{
_canCombust = true;
}
// ----- Heat loss to cylinder walls -----
// Heat loss
double dQ_loss = HeatTransferCoefficient * CylinderWallArea *
(Temperature - AmbientTemperature) * dt;
cylinderEnergy -= dQ_loss;
@@ -216,39 +216,46 @@ namespace FluidSim.Components
// Update port states
double p = Pressure, rho = Density, T = Temperature;
double h = Gamma / (Gamma - 1.0) * p / Math.Max(rho, 1e-12);
double af = AirFraction;
IntakePort.Pressure = p;
IntakePort.Density = rho;
IntakePort.Temperature = T;
IntakePort.SpecificEnthalpy = h;
IntakePort.AirFraction = af;
ExhaustPort.Pressure = p;
ExhaustPort.Density = rho;
ExhaustPort.Temperature = T;
ExhaustPort.SpecificEnthalpy = h;
ExhaustPort.AirFraction = af;
}
public void UpdateState(double dt)
{
double dm = 0.0;
double dE = 0.0;
double dmAir = 0.0, dmExhaust = 0.0, dE = 0.0;
foreach (var port in _ports)
{
dm += port.MassFlowRate * dt;
dE += port.MassFlowRate * port.SpecificEnthalpy * dt;
double mdot = port.MassFlowRate;
double af = mdot >= 0 ? port.AirFraction : AirFraction;
dmAir += mdot * af * dt;
dmExhaust += mdot * (1.0 - af) * dt;
dE += mdot * port.SpecificEnthalpy * dt;
}
cylinderMass += dm;
_airMass += dmAir;
_exhaustMass += dmExhaust;
cylinderEnergy += dE;
double V = Math.Max(cylinderVolume, 1e-12);
// --- Absolute pressure & temperature clamps ---
// Safety clamps
double currentP = (Gamma - 1.0) * cylinderEnergy / V;
if (currentP > MaxPressurePa)
cylinderEnergy = MaxPressurePa * V / (Gamma - 1.0);
double currentRho = cylinderMass / V;
double currentRho = (_airMass + _exhaustMass) / V;
double currentT = currentP / Math.Max(currentRho * GasConstant, 1e-12);
if (currentT > MaxTemperatureK)
{
@@ -256,10 +263,11 @@ namespace FluidSim.Components
cylinderEnergy = pAtTlimit * V / (Gamma - 1.0);
}
// Existing safeguards
if (cylinderMass < 1e-9)
double totalMass = _airMass + _exhaustMass;
if (totalMass < 1e-9)
{
cylinderMass = 1e-9;
_airMass = 1e-9;
_exhaustMass = 0.0;
cylinderEnergy = 101325.0 * V / (Gamma - 1.0);
}
else if (cylinderEnergy < 0.0)
@@ -267,8 +275,8 @@ namespace FluidSim.Components
cylinderEnergy = 101325.0 * V / (Gamma - 1.0);
}
if (cylinderMass < 0.0) cylinderMass = 1e-9;
if (cylinderEnergy < 0.0) cylinderEnergy = 101325.0 * V / (Gamma - 1.0);
if (_airMass < 0.0) _airMass = 0.0;
if (_exhaustMass < 0.0) _exhaustMass = 0.0;
}
}
}

View File

@@ -7,10 +7,10 @@ namespace FluidSim.Components
/// <summary>
/// 1D compressible Euler pipe with LaxFriedrichs finitevolume scheme.
/// Ghost states are set externally via SetGhostLeft/Right; they are always required.
/// Now includes a passive scalar for air mass fraction.
/// </summary>
public class Pipe1D : IComponent
{
// ---------- Compiletime profiling flag ----------
public const bool EnableDetailedProfiling = false; // set to false in release builds
public Port PortA { get; }
@@ -36,10 +36,11 @@ namespace FluidSim.Components
private readonly double _gamma = 1.4;
private double[] _rho, _rhou, _E;
private double[] _fluxM, _fluxP, _fluxE; // flux at cell faces (0.._n) kept for possible external use, not used internally anymore
private double[] _Y; // air mass fraction
private double[] _fluxM, _fluxP, _fluxE;
private double _rhoGhostL, _uGhostL, _pGhostL;
private double _rhoGhostR, _uGhostR, _pGhostR;
private double _rhoGhostL, _uGhostL, _pGhostL, _YGhostL;
private double _rhoGhostR, _uGhostR, _pGhostR, _YGhostR;
private bool _ghostLValid, _ghostRValid;
private double _laminarCoeff;
@@ -65,7 +66,7 @@ namespace FluidSim.Components
_rho = new double[_n];
_rhou = new double[_n];
_E = new double[_n];
_Y = new double[_n];
_fluxM = new double[_n + 1];
_fluxP = new double[_n + 1];
_fluxE = new double[_n + 1];
@@ -87,16 +88,19 @@ namespace FluidSim.Components
public void UpdateState(double dt) { }
// ---------- Ghost interface ----------
public void SetGhostLeft(double rho, double u, double p)
public void SetGhostLeft(double rho, double u, double p, double airFraction)
{
_rhoGhostL = rho; _uGhostL = u; _pGhostL = p; _ghostLValid = true;
_rhoGhostL = rho; _uGhostL = u; _pGhostL = p; _YGhostL = airFraction; _ghostLValid = true;
}
public void SetGhostRight(double rho, double u, double p)
public void SetGhostRight(double rho, double u, double p, double airFraction)
{
_rhoGhostR = rho; _uGhostR = u; _pGhostR = p; _ghostRValid = true;
_rhoGhostR = rho; _uGhostR = u; _pGhostR = p; _YGhostR = airFraction; _ghostRValid = true;
}
public void ClearGhostFlags() { _ghostLValid = false; _ghostRValid = false; }
public double GetInteriorAirFractionLeft() => _Y[0];
public double GetInteriorAirFractionRight() => _Y[_n - 1];
public (double rho, double u, double p) GetInteriorStateLeft()
{
double rho = Math.Max(_rho[0], 1e-12);
@@ -172,6 +176,34 @@ namespace FluidSim.Components
t0 = t1;
}
// ---------- Local flux functions ----------
void LaxFlux(double rL, double uL, double pL, double cL,
double rR, double uR, double pR, double cR,
out double fm, out double fp, out double fe)
{
double EL = pL / (gm1 * rL) + 0.5 * uL * uL;
double ER = pR / (gm1 * rR) + 0.5 * uR * uR;
double Fm_L = rL * uL;
double Fp_L = rL * uL * uL + pL;
double Fe_L = (rL * EL + pL) * uL;
double Fm_R = rR * uR;
double Fp_R = rR * uR * uR + pR;
double Fe_R = (rR * ER + pR) * uR;
double alpha = Math.Max(Math.Abs(uL) + cL, Math.Abs(uR) + cR);
fm = 0.5 * (Fm_L + Fm_R) - 0.5 * alpha * (rR - rL);
fp = 0.5 * (Fp_L + Fp_R) - 0.5 * alpha * (rR * uR - rL * uL);
fe = 0.5 * (Fe_L + Fe_R) - 0.5 * alpha * (rR * ER - rL * EL);
}
void ScalarFlux(double rL, double uL, double cL, double YL,
double rR, double uR, double cR, double YR,
double alpha, out double fy)
{
double Fm_L = rL * uL;
double Fm_R = rR * uR;
fy = 0.5 * (Fm_L * YL + Fm_R * YR) - 0.5 * alpha * (rR * YR - rL * YL);
}
// ---------- Phase 2: Left face flux (ghostL cell 0) ----------
double rL_ghost = Math.Max(_rhoGhostL, 1e-12);
double pL_ghost = _pGhostL;
@@ -182,6 +214,12 @@ namespace FluidSim.Components
_rho[0], _rhou[0] / Math.Max(_rho[0], 1e-12), p[0], c[0],
out double fluxM_left, out double fluxP_left, out double fluxE_left);
double alphaLeft = Math.Max(Math.Abs(uL_ghost) + cL_ghost,
Math.Abs(_rhou[0] / Math.Max(_rho[0], 1e-12)) + c[0]);
ScalarFlux(rL_ghost, uL_ghost, cL_ghost, _YGhostL,
_rho[0], _rhou[0] / Math.Max(_rho[0], 1e-12), c[0], _Y[0],
alphaLeft, out double fluxY_left);
if (EnableDetailedProfiling)
{
t1 = Stopwatch.GetTimestamp();
@@ -193,6 +231,7 @@ namespace FluidSim.Components
double fluxM_prev = fluxM_left;
double fluxP_prev = fluxP_left;
double fluxE_prev = fluxE_left;
double fluxY_prev = fluxY_left;
for (int i = 0; i < n - 1; i++)
{
@@ -203,23 +242,31 @@ namespace FluidSim.Components
double uL = _rhou[iL] / rL;
double pL = p[iL];
double cL = c[iL];
double YL = _Y[iL];
double rR = Math.Max(_rho[iR], 1e-12);
double uR = _rhou[iR] / rR;
double pR = p[iR];
double cR = c[iR];
double YR = _Y[iR];
LaxFlux(rL, uL, pL, cL, rR, uR, pR, cR,
out double fluxM_right, out double fluxP_right, out double fluxE_right);
double alpha = Math.Max(Math.Abs(uL) + cL, Math.Abs(uR) + cR);
ScalarFlux(rL, uL, cL, YL, rR, uR, cR, YR, alpha, out double fluxY_right);
// Update cell i
double r = _rho[i];
double ru = _rhou[i];
double E = _E[i];
double Y = _Y[i];
double newR = r - dt_dx * (fluxM_right - fluxM_prev);
double newRu = ru - dt_dx * (fluxP_right - fluxP_prev);
double newE = E - dt_dx * (fluxE_right - fluxE_prev);
double oldRhoY = r * Y;
double newRhoY = oldRhoY - dt_dx * (fluxY_right - fluxY_prev);
double dampingFactor = Math.Exp(-coeff / Math.Max(r, 1e-12) * dt);
newRu *= dampingFactor;
@@ -234,10 +281,12 @@ namespace FluidSim.Components
_rho[i] = newR;
_rhou[i] = newRu;
_E[i] = newE;
_Y[i] = Math.Clamp(newRhoY / newR, 0.0, 1.0);
fluxM_prev = fluxM_right;
fluxP_prev = fluxP_right;
fluxE_prev = fluxE_right;
fluxY_prev = fluxY_right;
}
if (EnableDetailedProfiling)
@@ -253,20 +302,31 @@ namespace FluidSim.Components
double uR_ghost = _uGhostR;
double cR_ghost = Math.Sqrt(gamma * pR_ghost / rR_ghost);
LaxFlux(_rho[n - 1], _rhou[n - 1] / Math.Max(_rho[n - 1], 1e-12), p[n - 1], c[n - 1],
double rInt = _rho[n - 1];
double uInt = _rhou[n - 1] / Math.Max(rInt, 1e-12);
LaxFlux(rInt, uInt, p[n - 1], c[n - 1],
rR_ghost, uR_ghost, pR_ghost, cR_ghost,
out double fluxM_right_final, out double fluxP_right_final, out double fluxE_right_final);
// Update last cell (identical to interior, but with final fluxes)
double alphaRight = Math.Max(Math.Abs(uInt) + c[n - 1], Math.Abs(uR_ghost) + cR_ghost);
ScalarFlux(rInt, uInt, c[n - 1], _Y[n - 1],
rR_ghost, uR_ghost, cR_ghost, _YGhostR,
alphaRight, out double fluxY_right_final);
// Update last cell
{
int i = n - 1;
double r = _rho[i];
double ru = _rhou[i];
double E = _E[i];
double Y = _Y[i];
double newR = r - dt_dx * (fluxM_right_final - fluxM_prev);
double newRu = ru - dt_dx * (fluxP_right_final - fluxP_prev);
double newE = E - dt_dx * (fluxE_right_final - fluxE_prev);
double oldRhoY = r * Y;
double newRhoY = oldRhoY - dt_dx * (fluxY_right_final - fluxY_prev);
double dampingFactor = Math.Exp(-coeff / Math.Max(r, 1e-12) * dt);
newRu *= dampingFactor;
@@ -281,6 +341,7 @@ namespace FluidSim.Components
_rho[i] = newR;
_rhou[i] = newRu;
_E[i] = newE;
_Y[i] = Math.Clamp(newRhoY / newR, 0.0, 1.0);
}
if (EnableDetailedProfiling)
@@ -295,11 +356,13 @@ namespace FluidSim.Components
PortA.Pressure = pA; PortA.Density = rhoA;
PortA.Temperature = pA / (rhoA * 287.0);
PortA.SpecificEnthalpy = gm1 / (gamma - 1.0) * pA / rhoA;
PortA.AirFraction = _Y[0];
(double rhoB, double uB, double pB) = GetInteriorStateRight();
PortB.Pressure = pB; PortB.Density = rhoB;
PortB.Temperature = pB / (rhoB * 287.0);
PortB.SpecificEnthalpy = gm1 / (gamma - 1.0) * pB / rhoB;
PortB.AirFraction = _Y[_n - 1];
if (EnableDetailedProfiling)
{
@@ -308,48 +371,6 @@ namespace FluidSim.Components
}
}
// ---------- Local LaxFriedrichs flux function ----------
private void LaxFlux(double rL, double uL, double pL, double cL,
double rR, double uR, double pR, double cR,
out double fm, out double fp, out double fe)
{
double gm1 = _gamma - 1.0;
double EL = pL / (gm1 * rL) + 0.5 * uL * uL;
double ER = pR / (gm1 * rR) + 0.5 * uR * uR;
double Fm_L = rL * uL;
double Fp_L = rL * uL * uL + pL;
double Fe_L = (rL * EL + pL) * uL;
double Fm_R = rR * uR;
double Fp_R = rR * uR * uR + pR;
double Fe_R = (rR * ER + pR) * uR;
double alpha = Math.Max(Math.Abs(uL) + cL, Math.Abs(uR) + cR);
fm = 0.5 * (Fm_L + Fm_R) - 0.5 * alpha * (rR - rL);
fp = 0.5 * (Fp_L + Fp_R) - 0.5 * alpha * (rR * uR - rL * uL);
fe = 0.5 * (Fe_L + Fe_R) - 0.5 * alpha * (rR * ER - rL * EL);
}
// Original LaxFriedrichsFlux (kept for compatibility, can be removed if unused)
private void LaxFriedrichsFlux(double rL, double uL, double pL, double eL,
double rR, double uR, double pR, double eR,
out double fm, out double fp, out double fe)
{
double rhoL = rL, rhoR = rR;
double EL = rhoL * eL;
double ER = rhoR * eR;
double Fm_L = rhoL * uL;
double Fp_L = rhoL * uL * uL + pL;
double Fe_L = (EL + pL) * uL;
double Fm_R = rhoR * uR;
double Fp_R = rhoR * uR * uR + pR;
double Fe_R = (ER + pR) * uR;
double cL = Math.Sqrt(_gamma * pL / rL);
double cR = Math.Sqrt(_gamma * pR / rR);
double alpha = Math.Max(Math.Abs(uL) + cL, Math.Abs(uR) + cR);
fm = 0.5 * (Fm_L + Fm_R) - 0.5 * alpha * (rhoR - rhoL);
fp = 0.5 * (Fp_L + Fp_R) - 0.5 * alpha * (rhoR * uR - rhoL * uL);
fe = 0.5 * (Fe_L + Fe_R) - 0.5 * alpha * (ER - EL);
}
private double PressureScalar(int i)
{
double rho = Math.Max(_rho[i], 1e-12);
@@ -365,6 +386,7 @@ namespace FluidSim.Components
_rho[i] = rho;
_rhou[i] = rho * u;
_E[i] = E;
_Y[i] = 1.0; // initially pure air
}
}
@@ -376,6 +398,7 @@ namespace FluidSim.Components
_rho[i] = rho;
_rhou[i] = rho * u;
_E[i] = E;
_Y[i] = 1.0;
}
public void SetCellPressure(int i, double p)

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using FluidSim.Interfaces;
@@ -8,8 +8,9 @@ namespace FluidSim.Components
{
public List<Port> Ports { get; } = new List<Port>();
public double Mass { get; set; } // made public setter
public double InternalEnergy { get; set; } // made public setter
private double _airMass;
private double _exhaustMass;
public double InternalEnergy { get; set; }
public double Volume { get; set; }
public double Dvdt { get; set; }
public double Gamma { get; set; } = 1.4;
@@ -18,6 +19,8 @@ namespace FluidSim.Components
public double AmbientPressure { get; set; } = 101325.0;
// Derived quantities
public double Mass => _airMass + _exhaustMass;
public double AirFraction => _airMass / Math.Max(Mass, 1e-12);
public double Density => Mass / Math.Max(Volume, 1e-12);
public double Pressure => (Gamma - 1.0) * InternalEnergy / Math.Max(Volume, 1e-12);
public double Temperature => Pressure / Math.Max(Density * GasConstant, 1e-12);
@@ -32,7 +35,8 @@ namespace FluidSim.Components
Dvdt = 0.0;
double rho0 = initialPressure / (GasConstant * initialTemperature);
Mass = rho0 * Volume;
_airMass = rho0 * Volume; // starts with all air
_exhaustMass = 0.0;
InternalEnergy = (initialPressure * Volume) / (Gamma - 1.0);
}
@@ -43,44 +47,53 @@ namespace FluidSim.Components
port.Density = Density;
port.Temperature = Temperature;
port.SpecificEnthalpy = SpecificEnthalpy;
port.AirFraction = AirFraction;
Ports.Add(port);
return port;
}
/// <summary>
/// Set the pressure to a specific value while keeping the current temperature constant.
/// Updates Mass and InternalEnergy accordingly.
/// </summary>
public void SetPressure(double pressure, double? temperature = null)
{
double V = Math.Max(Volume, 1e-12);
double T = temperature ?? Temperature;
double rho = pressure / (GasConstant * T);
Mass = rho * V;
double totalMass = rho * V;
// Keep current air fraction when setting pressure?
double af = AirFraction;
_airMass = totalMass * af;
_exhaustMass = totalMass * (1.0 - af);
InternalEnergy = pressure * V / (Gamma - 1.0);
}
public void UpdateState(double dt)
{
double totalMdot = 0.0;
double totalMdotAir = 0.0;
double totalMdotExhaust = 0.0;
double totalEdot = 0.0;
foreach (var port in Ports)
{
totalMdot += port.MassFlowRate;
totalEdot += port.MassFlowRate * port.SpecificEnthalpy;
double mdot = port.MassFlowRate; // positive INTO volume
double af = mdot >= 0 ? port.AirFraction : AirFraction; // inflow: use port's fraction; outflow: well-mixed
totalMdotAir += mdot * af;
totalMdotExhaust += mdot * (1.0 - af);
totalEdot += mdot * port.SpecificEnthalpy;
}
double dm = totalMdot * dt;
double dAir = totalMdotAir * dt;
double dExhaust = totalMdotExhaust * dt;
double dE = totalEdot * dt - Pressure * Dvdt * dt;
Mass += dm;
_airMass += dAir;
_exhaustMass += dExhaust;
InternalEnergy += dE;
double V = Math.Max(Volume, 1e-12);
if (Mass < 1e-9)
double totalMass = _airMass + _exhaustMass;
if (totalMass < 1e-9)
{
Mass = 1e-9;
_airMass = 1e-9;
_exhaustMass = 0.0;
InternalEnergy = AmbientPressure * V / (Gamma - 1.0);
}
else if (InternalEnergy < 0.0)
@@ -88,16 +101,17 @@ namespace FluidSim.Components
InternalEnergy = AmbientPressure * V / (Gamma - 1.0);
}
if (Mass < 0.0) Mass = 1e-9;
if (InternalEnergy < 0.0) InternalEnergy = AmbientPressure * V / (Gamma - 1.0);
if (_airMass < 0.0) _airMass = 0.0;
if (_exhaustMass < 0.0) _exhaustMass = 0.0;
double p = Pressure, rho = Density, T = Temperature, h = SpecificEnthalpy;
double p = Pressure, rho = Density, T = Temperature, h = SpecificEnthalpy, afrac = AirFraction;
foreach (var port in Ports)
{
port.Pressure = p;
port.Density = rho;
port.Temperature = T;
port.SpecificEnthalpy = h;
port.AirFraction = afrac;
}
}