Skip to content

Commit 33a3e1c

Browse files
committed
Add model for calculating the amount of steam used to heat fuel oil in the tender
1 parent 1985cbe commit 33a3e1c

File tree

1 file changed

+125
-11
lines changed

1 file changed

+125
-11
lines changed

Source/Orts.Simulation/Simulation/RollingStocks/MSTSSteamLocomotive.cs

Lines changed: 125 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ public class MSTSSteamLocomotive : MSTSLocomotive
230230
public float CylinderSteamUsageLBpS;
231231
public float NewCylinderSteamUsageLBpS;
232232
public float BlowerSteamUsageLBpS;
233+
public float FuelOilHeatingSteamUsageLbpS;
233234
public float EvaporationLBpS; // steam generation rate
234235
public float FireMassKG; // Mass of coal currently on grate area
235236
public float FireRatio; // Ratio of actual firemass to ideal firemass
@@ -3582,6 +3583,52 @@ protected override void UpdateControllers(float elapsedClockSeconds)
35823583

35833584
private void UpdateTender(float elapsedClockSeconds)
35843585
{
3586+
// Calculate steam usage required to heat fuel oil in oil fired locomotive
3587+
if (SteamLocomotiveFuelType == SteamLocomotiveFuelTypes.Oil )
3588+
{
3589+
// The following calculations are based upon the interpretation of information in
3590+
// https://www.spiraxsarco.com/learn-about-steam/steam-engineering-principles-and-heat-transfer/heating-with-coils-and-jackets#article-top
3591+
// and movement of the locomotive is described in this article
3592+
// https://www.spiraxsarco.com/learn-about-steam/steam-engineering-principles-and-heat-transfer/energy-consumption-of-tanks-and-vats
3593+
// Currently due to the difficulty of finding input information some assumptions have be made, and will be described through the calculation process.
3594+
//
3595+
// The first step is to calculate the amount of heat lost through the sides of the oil tank on the tender (this loss will ultimately need to be
3596+
// compensated by steam heating
3597+
// This can be calculated by Q (heat Loss) = Txf Coeff for tank sides * Area of Sides * Temp Diff (external vs internal)
3598+
// The most accurate calculation could be made if the bottom, sides and top of oil tank area was known, and also if the aount of exposed surface
3599+
// (subject to wind effect)
3600+
float TankSurfaceAreatoMassRatio = 0.015f; // based upon the inspection of the oil masses in a couple of known locomotives
3601+
float AssumedSurfaceAreaFt2 = Kg.ToLb(MaxTenderOilMassL * OilSpecificGravity) * TankSurfaceAreatoMassRatio;
3602+
float AreaExposedtoWindMovementFraction = 0.42f;
3603+
float OilTempRequired = 150; // Oil to be heated to 150degF
3604+
float HeatDiff = OilTempRequired - C.ToF(CarOutsideTempC);
3605+
float HeatTransferCoefficientBtuphft2F = 0.00001263f * HeatDiff * HeatDiff + 0.001175f * HeatDiff + 1.776f;
3606+
3607+
float HeatLossNoWindBTUph = (1- AreaExposedtoWindMovementFraction) * HeatTransferCoefficientBtuphft2F * AssumedSurfaceAreaFt2 * HeatDiff;
3608+
3609+
// To compensate for the train movement we need to add a wind factor
3610+
float WindCoeff = -0.0074f * absSpeedMpS * absSpeedMpS + 0.3817f * absSpeedMpS + 1f;
3611+
WindCoeff = MathHelper.Clamp(WindCoeff, 1.0f, 5.78f); // Wind speed effect will not cause any more impact once over about 25 m/s
3612+
3613+
float HeatLossWindBTUph = (1 - AreaExposedtoWindMovementFraction) * HeatTransferCoefficientBtuphft2F * AssumedSurfaceAreaFt2 * HeatDiff * WindCoeff;
3614+
3615+
float HeatLossTotalBTUph = HeatLossWindBTUph + HeatLossNoWindBTUph;
3616+
3617+
float HeatingCoilEfficiency = 0.12f;
3618+
float SteamEnthalpyBTUpLb = 980;
3619+
FuelOilHeatingSteamUsageLbpS = pS.FrompH(HeatLossTotalBTUph / (HeatingCoilEfficiency * SteamEnthalpyBTUpLb));
3620+
3621+
// adjust boiler heat and volume due to steam usage
3622+
BoilerMassLB -= elapsedClockSeconds * FuelOilHeatingSteamUsageLbpS; // Reduce boiler mass to reflect steam usage by fuel oil heating coils
3623+
BoilerHeatBTU -= elapsedClockSeconds * FuelOilHeatingSteamUsageLbpS * (BoilerSteamHeatBTUpLB - BoilerWaterHeatBTUpLB); // Reduce boiler Heat to reflect steam usage by fuel oil heating coils
3624+
BoilerHeatOutBTUpS += FuelOilHeatingSteamUsageLbpS * (BoilerSteamHeatBTUpLB - BoilerWaterHeatBTUpLB); // Reduce boiler Heat to reflect steam usage by mecahnical stoker
3625+
TotalSteamUsageLBpS += FuelOilHeatingSteamUsageLbpS;
3626+
3627+
// Increase tender water mass as steam heating condensate feed back into tender
3628+
CombinedTenderWaterVolumeUKG += (elapsedClockSeconds * FuelOilHeatingSteamUsageLbpS) / WaterLBpUKG;
3629+
3630+
}
3631+
35853632
TenderWaterLevelFraction = CombinedTenderWaterVolumeUKG / MaxTotalCombinedWaterVolumeUKG;
35863633
float TempCylinderSteamUsageLbpS = CylinderSteamUsageLBpS;
35873634
// Limit Cylinder steam usage to the maximum boiler evaporation rate, lower limit is for when the locomotive is at rest and "no steam" is being used by cylinder, ensures some coal is used.
@@ -7606,23 +7653,87 @@ public override string GetDebugStatus()
76067653

76077654
if (!(BrakeSystem is Orts.Simulation.RollingStocks.SubSystems.Brakes.MSTS.VacuumSinglePipe))
76087655
{
7609-
// Display air compressor information
7610-
status.AppendFormat("{0}\t{1}\t{2}/{23}\t{3}\t{4}/{23}\t{5}\t{6}/{23}\t{7}\t{8}/{23}\t{9}\t{10}/{23}\t{11}\t{12}/{23}\t{13}\t{14}/{23}\t{15}\t{16}/{23}\t{17}\t{18}/{23}\t{19}\t{20}/{23}\t({21}x{22:N1}\")\n",
7656+
if (SteamLocomotiveFuelType == SteamLocomotiveFuelTypes.Oil)
7657+
{
7658+
// Display air compressor information
7659+
status.AppendFormat("{0}\t{1}\t{2}/{23}\t{3}\t{4}/{23}\t{5}\t{6}/{23}\t{7}\t{8}/{23}\t{9}\t{10}/{23}\t{11}\t{12}/{23}\t{13}\t{14}/{23}\t{15}\t{16}/{23}\t{17}\t{18}/{23}\t{19}\t{20}/{23}\t({21}x{22:N1}\")\n",
7660+
Simulator.Catalog.GetString("Usage:"),
7661+
Simulator.Catalog.GetString("Cyl"),
7662+
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(CylinderSteamUsageLBpS)), IsMetric),
7663+
Simulator.Catalog.GetString("Blower"),
7664+
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(BlowerSteamUsageLBpS)), IsMetric),
7665+
Simulator.Catalog.GetString("Comprsr"),
7666+
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(CompSteamUsageLBpS)), IsMetric),
7667+
Simulator.Catalog.GetString("SafetyV"),
7668+
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(SafetyValveUsageLBpS)), IsMetric),
7669+
Simulator.Catalog.GetString("CylCock"),
7670+
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(CylCockSteamUsageDisplayLBpS)), IsMetric),
7671+
Simulator.Catalog.GetString("Genertr"),
7672+
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(GeneratorSteamUsageLBpS)), IsMetric),
7673+
Simulator.Catalog.GetString("OilHeat"),
7674+
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(FuelOilHeatingSteamUsageLbpS)), IsMetric),
7675+
Simulator.Catalog.GetString("BlowD"),
7676+
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(BlowdownSteamUsageLBpS)), IsMetric),
7677+
Simulator.Catalog.GetString("Booster"),
7678+
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(HuDBoosterSteamConsumptionLbpS)), IsMetric),
7679+
Simulator.Catalog.GetString("MaxSafe"),
7680+
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(MaxSafetyValveDischargeLbspS)), IsMetric),
7681+
NumSafetyValves,
7682+
SafetyValveSizeIn,
7683+
FormatStrings.h);
7684+
}
7685+
else
7686+
{
7687+
// Display air compressor information with stoker fired locomotive
7688+
status.AppendFormat("{0}\t{1}\t{2}/{23}\t{3}\t{4}/{23}\t{5}\t{6}/{23}\t{7}\t{8}/{23}\t{9}\t{10}/{23}\t{11}\t{12}/{23}\t{13}\t{14}/{23}\t{15}\t{16}/{23}\t{17}\t{18}/{23}\t{19}\t{20}/{23}\t({21}x{22:N1}\")\n",
7689+
Simulator.Catalog.GetString("Usage:"),
7690+
Simulator.Catalog.GetString("Cyl"),
7691+
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(CylinderSteamUsageLBpS)), IsMetric),
7692+
Simulator.Catalog.GetString("Blower"),
7693+
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(BlowerSteamUsageLBpS)), IsMetric),
7694+
Simulator.Catalog.GetString("Comprsr"),
7695+
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(CompSteamUsageLBpS)), IsMetric),
7696+
Simulator.Catalog.GetString("SafetyV"),
7697+
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(SafetyValveUsageLBpS)), IsMetric),
7698+
Simulator.Catalog.GetString("CylCock"),
7699+
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(CylCockSteamUsageDisplayLBpS)), IsMetric),
7700+
Simulator.Catalog.GetString("Genertr"),
7701+
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(GeneratorSteamUsageLBpS)), IsMetric),
7702+
Simulator.Catalog.GetString("Stoker"),
7703+
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(StokerSteamUsageLBpS)), IsMetric),
7704+
Simulator.Catalog.GetString("BlowD"),
7705+
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(BlowdownSteamUsageLBpS)), IsMetric),
7706+
Simulator.Catalog.GetString("Booster"),
7707+
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(HuDBoosterSteamConsumptionLbpS)), IsMetric),
7708+
Simulator.Catalog.GetString("MaxSafe"),
7709+
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(MaxSafetyValveDischargeLbspS)), IsMetric),
7710+
NumSafetyValves,
7711+
SafetyValveSizeIn,
7712+
FormatStrings.h);
7713+
}
7714+
}
7715+
else
7716+
{
7717+
7718+
if (SteamLocomotiveFuelType == SteamLocomotiveFuelTypes.Oil)
7719+
{
7720+
// Display steam ejector information instead of air compressor with stoker
7721+
status.AppendFormat("{0}\t{1}\t{2}/{23}\t{3}\t{4}/{23}\t{5}\t{6}/{23}\t{7}\t{8}/{23}\t{9}\t{10}/{23}\t{11}\t{12}/{23}\t{13}\t{14}/{23}\t{15}\t{16}/{23}\t{17}\t{18}/{23}\t{19}\t{20}/{23}\t({21}x{22:N1}\")\n",
76117722
Simulator.Catalog.GetString("Usage:"),
76127723
Simulator.Catalog.GetString("Cyl"),
76137724
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(CylinderSteamUsageLBpS)), IsMetric),
76147725
Simulator.Catalog.GetString("Blower"),
76157726
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(BlowerSteamUsageLBpS)), IsMetric),
7616-
Simulator.Catalog.GetString("Comprsr"),
7617-
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(CompSteamUsageLBpS)), IsMetric),
7727+
Simulator.Catalog.GetString("Ejector"),
7728+
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(EjectorTotalSteamConsumptionLbpS)), IsMetric),
76187729
Simulator.Catalog.GetString("SafetyV"),
76197730
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(SafetyValveUsageLBpS)), IsMetric),
76207731
Simulator.Catalog.GetString("CylCock"),
76217732
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(CylCockSteamUsageDisplayLBpS)), IsMetric),
76227733
Simulator.Catalog.GetString("Genertr"),
76237734
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(GeneratorSteamUsageLBpS)), IsMetric),
7624-
Simulator.Catalog.GetString("Stoker"),
7625-
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(StokerSteamUsageLBpS)), IsMetric),
7735+
Simulator.Catalog.GetString("OilHeat"),
7736+
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(FuelOilHeatingSteamUsageLbpS)), IsMetric),
76267737
Simulator.Catalog.GetString("BlowD"),
76277738
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(BlowdownSteamUsageLBpS)), IsMetric),
76287739
Simulator.Catalog.GetString("Booster"),
@@ -7632,11 +7743,13 @@ public override string GetDebugStatus()
76327743
NumSafetyValves,
76337744
SafetyValveSizeIn,
76347745
FormatStrings.h);
7635-
}
7636-
else
7637-
{
7638-
// Display steam ejector information instead of air compressor
7639-
status.AppendFormat("{0}\t{1}\t{2}/{23}\t{3}\t{4}/{23}\t{5}\t{6}/{23}\t{7}\t{8}/{23}\t{9}\t{10}/{23}\t{11}\t{12}/{23}\t{13}\t{14}/{23}\t{15}\t{16}/{23}\t{17}\t{18}/{23}\t{19}\t{20}/{23}\t({21}x{22:N1}\")\n",
7746+
7747+
}
7748+
else
7749+
{
7750+
7751+
// Display steam ejector information instead of air compressor with stoker
7752+
status.AppendFormat("{0}\t{1}\t{2}/{23}\t{3}\t{4}/{23}\t{5}\t{6}/{23}\t{7}\t{8}/{23}\t{9}\t{10}/{23}\t{11}\t{12}/{23}\t{13}\t{14}/{23}\t{15}\t{16}/{23}\t{17}\t{18}/{23}\t{19}\t{20}/{23}\t({21}x{22:N1}\")\n",
76407753
Simulator.Catalog.GetString("Usage:"),
76417754
Simulator.Catalog.GetString("Cyl"),
76427755
FormatStrings.FormatMass(pS.TopH(Kg.FromLb(CylinderSteamUsageLBpS)), IsMetric),
@@ -7661,6 +7774,7 @@ public override string GetDebugStatus()
76617774
NumSafetyValves,
76627775
SafetyValveSizeIn,
76637776
FormatStrings.h);
7777+
}
76647778
}
76657779

76667780

0 commit comments

Comments
 (0)