Skip to content

Commit

Permalink
Merge pull request #908 from cjakeman/feature/advanced-adhesion-switc…
Browse files Browse the repository at this point in the history
…hing2

feat: supports switching adhesion precisions
  • Loading branch information
cjakeman authored Feb 6, 2024
2 parents bae4730 + ad3362c commit e0bf062
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 70 deletions.
16 changes: 11 additions & 5 deletions Source/Documentation/Manual/physics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -271,12 +271,17 @@ wheel adhesion. The first model is based upon an algorithm by Pacha, whilst the
uses an algorithm developed by Polach. The Polach algorithm provides
a more accurate outcome and facilitates the future inclusion of track conditions.
However due to the number of algorithm steps required to calculate the wheel adhesion
value, it is more CPU load intensive then the Pacha one. This can produce low
frame rates for the screen display in machines with low performance specifications.
value, it is more CPU load-intensive then the Pacha one. On low performance PCs, this would lower the
frame rate for the screen display to an unacceptable degree.

Hence OR automatically senses the CPU load, and switches to the Pacha algorithm at
high loads and to the Polach algorithm under lower CPU loads. In this way OR attempts
to support the operation of lower specification computers. When OR is using the
To avoid this, OR senses the frame rate and switches from the Polach algorithm
to the Pacha one as follows.
If the frame rate falls below 30 fps, then a switch is made to Pacha until the frame rate
recovers to more than 40 fps. If a switch to Pacha happens more than once in a 5 minute interval
then it will persist for the rest of the session.

In this way OR provides a more accurate algorithm whilst retaining
the original one for lower specification computers. When OR is using the
Pacha algorithm, the "Wheel Adh (Max)" values will both read 99%, whereas when the
Polach algorithm is being used these values will be around the expected values of 30-55%.

Expand All @@ -288,6 +293,7 @@ The heart of the adhesion algorithm is the slip characteristics (pictured below)
:align: center
:scale: 70%


The *wheel creep* describes the stable area of the characteristics and is
used in the most of the operation time. When the tractive force reaches
the actual maximum of the slip characteristics, force transition falls
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,23 +295,105 @@ public void Restore(BinaryReader inf)
AxleList[i].Restore(inf);
}
}

/// <summary>
/// switch between Polach and Pacha adhesion calculation
/// </summary>
public static bool UsePolachAdhesion = false; // "static" so there's only one value in the program.
public bool PreviousUsePolachAdhesion = false; // Keep a note for each Axles so that we can tell if it changed.

/// <summary>
/// Updates each axle on the list
/// </summary>
/// <param name="elapsedClockSeconds">Time span within the simulation cycle</param>
public void Update(float elapsedClockSeconds)
/// <param name="elapsedSeconds">Time span within the simulation cycle</param>
public void Update(float elapsedSeconds)
{
UsePolachAdhesion = AdhesionPrecision.IsPrecisionHigh(this, elapsedSeconds, Car.Simulator.GameTime);
foreach (var axle in AxleList)
{
axle.Update(elapsedClockSeconds);
if (UsePolachAdhesion != PreviousUsePolachAdhesion) // There's been a transition
{
axle.AxleSpeedMpS = axle.TrainSpeedMpS; // So the transition doesn't cause a wheelslip
}
axle.Update(elapsedSeconds);
}
PreviousUsePolachAdhesion = UsePolachAdhesion;
}
public List<Axle>.Enumerator GetEnumerator()
{
return AxleList.GetEnumerator();
}
}

static class AdhesionPrecision // "static" so all "Axles" share the same level of precision
{
enum AdhesionPrecisionLevel
{
/// <summary>
/// Initial level uses Polach algorithm
/// </summary>
High = 0,
/// <summary>
/// Low-performance PCs use Pacha's algorithm
/// </summary>
Low = 1,
/// <summary>
/// After frequent transitions, low-performance PCs are locked to Pacha's algorithm
/// </summary>
LowLocked = 2
}

// Adjustable limits
const float LowerLimitS = 0.025f; // timespan 0.025 = 40 fps screen rate, low timeSpan and high FPS
const float UpperLimitS = 0.033f; // timespan 0.033 = 30 fps screen rate, high timeSpan and low FPS
const double IntervalBetweenDowngradesLimitS = 5 * 60; // Locks in low precision if < 5 mins between downgrades

static AdhesionPrecisionLevel PrecisionLevel = AdhesionPrecisionLevel.High;
static double TimeOfLatestDowngrade = 0 - IntervalBetweenDowngradesLimitS; // Starts at -5 mins

// Tested by dropping the framerate below 30 fps interactively. Did this by opening and closing the HelpWindow after inserting
// Threading.Thread.Sleep(40);
// into HelpWindow.PrepareFrame() temporarily.
public static bool IsPrecisionHigh(Axles axles, float elapsedSeconds, double gameTime)
{
// Switches between Polach (high precision) adhesion model and Pacha (low precision) adhesion model depending upon the PC performance
switch (PrecisionLevel)
{
case AdhesionPrecisionLevel.High:
if (elapsedSeconds > UpperLimitS)
{
var screenFrameRate = 1 / elapsedSeconds;
var timeSincePreviousDowngradeS = gameTime - TimeOfLatestDowngrade;
if (timeSincePreviousDowngradeS < IntervalBetweenDowngradesLimitS)
{
Trace.TraceInformation($"At {gameTime:F0} secs, advanced adhesion model switched to low precision permanently after {timeSincePreviousDowngradeS:F0} secs since previous switch (less than limit of {IntervalBetweenDowngradesLimitS})");
PrecisionLevel = AdhesionPrecisionLevel.LowLocked;
}
else
{
TimeOfLatestDowngrade = gameTime;
Trace.TraceInformation($"At {gameTime:F0} secs, advanced adhesion model switched to low precision after low frame rate {screenFrameRate:F1} below limit {1 / UpperLimitS:F0}");
PrecisionLevel = AdhesionPrecisionLevel.Low;
}
}
break;

case AdhesionPrecisionLevel.Low:
if (elapsedSeconds > 0 // When debugging step by step, elapsedSeconds == 0, so test for that
&& elapsedSeconds < LowerLimitS)
{
PrecisionLevel = AdhesionPrecisionLevel.High;
var ScreenFrameRate = 1 / elapsedSeconds;
Trace.TraceInformation($"At {gameTime:F0} secs, advanced adhesion model switched to high precision after high frame rate {ScreenFrameRate:F1} above limit {1 / LowerLimitS:F0}");
}
break;

case AdhesionPrecisionLevel.LowLocked:
break;
}
return (PrecisionLevel == AdhesionPrecisionLevel.High);
}
}
}


/// <summary>
Expand Down Expand Up @@ -446,11 +528,6 @@ public float InertiaKgm2
/// </summary>
float forceToAccelerationFactor;

/// <summary>
/// switch between Polach and Pacha adhesion calculation
/// </summary>
public static bool UsePolachAdhesion = false; // "static" so it's shared by all axles of the Player's loco

/// <summary>
/// Pre-calculation of slip characteristics at 0 slip speed
/// </summary>
Expand Down Expand Up @@ -578,7 +655,7 @@ public float TransmissionEfficiency
/// <summary>
/// Axle speed value, in metric meters per second
/// </summary>
public double AxleSpeedMpS { get; private set; }
public double AxleSpeedMpS { get; set; }

/// <summary>
/// Axle angular position in radians
Expand Down Expand Up @@ -860,7 +937,7 @@ public void Save(BinaryWriter outf)
double slipSpeedMpS = axleSpeedMpS - TrainSpeedMpS;
double axleOutForceN = 0;

if (UsePolachAdhesion)
if (Axles.UsePolachAdhesion)
{
axleOutForceN = Math.Sign(slipSpeedMpS) * AxleWeightN * SlipCharacteristicsPolach(slipSpeedMpS);
}
Expand Down Expand Up @@ -906,7 +983,7 @@ void Integrate(float elapsedClockSeconds)
if (elapsedClockSeconds <= 0) return;
double prevSpeedMpS = AxleSpeedMpS;

if (UsePolachAdhesion)
if (Axles.UsePolachAdhesion)
{

float upperSubStepLimit = 100;
Expand Down Expand Up @@ -995,7 +1072,7 @@ void Integrate(float elapsedClockSeconds)
{
var k1 = GetAxleMotionVariation(AxleSpeedMpS, dt);

if (i == 0 && !UsePolachAdhesion)
if (i == 0 && !Axles.UsePolachAdhesion)
{
if (k1.Item1 * dt > Math.Max((Math.Abs(SlipSpeedMpS) - 1) * 10, 1) / 100)
{
Expand Down Expand Up @@ -1038,8 +1115,7 @@ void Integrate(float elapsedClockSeconds)
/// <param name="elapsedSeconds"></param>
public virtual void Update(float elapsedSeconds)
{
UsePolachAdhesion = AdhesionPrecision.IsPrecisionHigh(elapsedSeconds);
if (UsePolachAdhesion)
if (Axles.UsePolachAdhesion)
{
forceToAccelerationFactor = WheelRadiusM * WheelRadiusM / totalInertiaKgm2;

Expand Down Expand Up @@ -1143,55 +1219,6 @@ public virtual void Update(float elapsedSeconds)
}
}

static class AdhesionPrecision // "static" so all "Axle"s share the same level of precision
{
enum AdhesionPrecisionLevel
{
/// <summary>
/// Initial level uses Polach algorithm
/// </summary>
High,
/// <summary>
/// Low-performance PCs use Pacha's algorithm
/// </summary>
Low
}

static AdhesionPrecisionLevel PrecisionLevel = AdhesionPrecisionLevel.High;
static double TimeOfLatestDowngrade = 0;

// Adjustable limits
const float UpperLimitS = 0.033f; // timespan 0.033 = 30 fps screen rate, high timeSpan and low FPS

// Tested by varying the framerate interactively. Did this by opening and closing the HelpWindow after inserting
// Threading.Thread.Sleep(40);
// into HelpWindow.PrepareFrame() temporarily.
public static bool IsPrecisionHigh(float elapsedSeconds)
{
if (elapsedSeconds > 0) // Ignore period with elapsedSeconds == 0 until user starts game.
{
// Switches between Polach (high precision) adhesion model and Pacha (low precision) adhesion model depending upon the PC performance
switch (PrecisionLevel)
{
case AdhesionPrecisionLevel.High:
if (elapsedSeconds > UpperLimitS)
{
var screenFrameRate = 1 / elapsedSeconds;
{
Trace.TraceInformation($"Advanced adhesion model switched to low precision permanently after low frame rate {screenFrameRate:F1} below limit {1 / UpperLimitS:F0}");
PrecisionLevel = AdhesionPrecisionLevel.Low;
}
}
break;

case AdhesionPrecisionLevel.Low:
break;
}
}
return (PrecisionLevel == AdhesionPrecisionLevel.High);
}
}

class PolachCalculator
{
Axle Axle;
Expand Down
2 changes: 1 addition & 1 deletion Source/RunActivity/Viewer3D/Popups/HUDWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1153,7 +1153,7 @@ void TextPageForceInfo(TableData table)
if (mstsLocomotive.AdvancedAdhesionModel)
{
var text = Viewer.Catalog.GetString("(Advanced adhesion model)");
if (Axle.UsePolachAdhesion == false) text += "???";
if (Axles.UsePolachAdhesion == false) text += "???";
TableAddLine(table, text);
int row0 = table.CurrentRow;
TableSetCell(table, table.CurrentRow++, table.CurrentLabelColumn, Viewer.Catalog.GetString("Wheel slip (Thres)"));
Expand Down

0 comments on commit e0bf062

Please sign in to comment.