Skip to content

Commit 452a72f

Browse files
committed
Use custom integrator to exploit particularities of the axle model
1 parent 05bff15 commit 452a72f

File tree

2 files changed

+60
-77
lines changed
  • Source
    • ORTS.Common
    • Orts.Simulation/Simulation/RollingStocks/SubSystems/PowerTransmissions

2 files changed

+60
-77
lines changed

Source/ORTS.Common/Integrator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ public float Integrate(float timeSpan, Func<float, float> value)
218218
{
219219
if (--waitBeforeSpeedingUp <= 0) //wait for a while before speeding up the integration
220220
{
221-
count = Math.Max(--numOfSubstepsPS, 5);
221+
count = Math.Max(--numOfSubstepsPS, 1);
222222

223223
waitBeforeSpeedingUp = 10; //not so fast ;)
224224
}

Source/Orts.Simulation/Simulation/RollingStocks/SubSystems/PowerTransmissions/Axle.cs

Lines changed: 59 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
using System;
1919
using System.IO;
20+
using Microsoft.Xna.Framework;
2021
using ORTS.Common;
2122

2223
namespace Orts.Simulation.RollingStocks.SubSystems.PowerTransmissions
@@ -53,11 +54,7 @@ public enum AxleDriveType
5354
/// </summary>
5455
public class Axle
5556
{
56-
/// <summary>
57-
/// Integrator used for axle dynamic solving
58-
/// </summary>
59-
public Integrator AxleRevolutionsInt = new Integrator(0.0f, IntegratorMethods.RungeKutta4);
60-
public int NumOfSubstepsPS { get { return AxleRevolutionsInt.NumOfSubstepsPS; } }
57+
public int NumOfSubstepsPS { get; set; }
6158

6259
/// <summary>
6360
/// Brake force covered by BrakeForceN interface
@@ -297,38 +294,17 @@ public float AdhesionK
297294
/// </summary>
298295
public float Adhesion2 { set; get; }
299296
/// <summary>
300-
/// Axle speed value, covered by AxleSpeedMpS interface, in metric meters per second
301-
/// </summary>
302-
float axleSpeedMpS;
303-
/// <summary>
304297
/// Axle speed value, in metric meters per second
305298
/// </summary>
306-
public float AxleSpeedMpS
307-
{
308-
set // used in initialisation at speed > = 0
309-
{
310-
axleSpeedMpS = value;
311-
}
312-
get
313-
{
314-
return axleSpeedMpS;
315-
}
316-
}
317-
299+
public float AxleSpeedMpS { get; set; }
318300
/// <summary>
319-
/// Axle force value, covered by AxleForceN interface, in Newtons
301+
/// Axle angular position in radians
320302
/// </summary>
321-
float axleForceN;
303+
public float AxlePositionRad { get; private set; }
322304
/// <summary>
323305
/// Read only axle force value, in Newtons
324306
/// </summary>
325-
public float AxleForceN
326-
{
327-
get
328-
{
329-
return axleForceN;
330-
}
331-
}
307+
public float AxleForceN { get; private set; }
332308

333309
/// <summary>
334310
/// Compensated Axle force value, this provided the motive force equivalent excluding brake force, in Newtons
@@ -402,7 +378,7 @@ public float SlipSpeedMpS
402378
{
403379
get
404380
{
405-
return (axleSpeedMpS - TrainSpeedMpS);
381+
return (AxleSpeedMpS - TrainSpeedMpS);
406382
}
407383
}
408384

@@ -458,6 +434,9 @@ public float SlipDerivationPercentpS
458434
}
459435
}
460436

437+
float integratorError;
438+
int waitBeforeSpeedingUp;
439+
461440
/// <summary>
462441
/// Read/Write relative slip speed warning threshold value, in percent of maximal effective slip
463442
/// </summary>
@@ -478,22 +457,16 @@ public Axle()
478457
transmissionEfficiency = 0.99f;
479458
SlipWarningTresholdPercent = 70.0f;
480459
driveType = AxleDriveType.ForceDriven;
481-
AxleRevolutionsInt.IsLimited = true;
482-
AxleRevolutionsInt.MaxSubsteps = 50;
483460
Adhesion2 = 0.331455f;
484461

485462
switch (driveType)
486463
{
487464
case AxleDriveType.NotDriven:
488465
break;
489466
case AxleDriveType.MotorDriven:
490-
AxleRevolutionsInt.Max = 5000.0f;
491-
AxleRevolutionsInt.Min = -5000.0f;
492467
totalInertiaKgm2 = inertiaKgm2 + transmissionRatio * transmissionRatio * motor.InertiaKgm2;
493468
break;
494469
case AxleDriveType.ForceDriven:
495-
AxleRevolutionsInt.Max = 1000.0f;
496-
AxleRevolutionsInt.Min = -1000.0f;
497470
totalInertiaKgm2 = inertiaKgm2;
498471
break;
499472
default:
@@ -516,7 +489,6 @@ public Axle(ElectricMotor electricMotor)
516489
motor.AxleConnected = this;
517490
transmissionEfficiency = 0.99f;
518491
driveType = AxleDriveType.MotorDriven;
519-
AxleRevolutionsInt.IsLimited = true;
520492
Adhesion2 = 0.331455f;
521493

522494
switch (driveType)
@@ -525,13 +497,9 @@ public Axle(ElectricMotor electricMotor)
525497
totalInertiaKgm2 = inertiaKgm2;
526498
break;
527499
case AxleDriveType.MotorDriven:
528-
AxleRevolutionsInt.Max = 5000.0f;
529-
AxleRevolutionsInt.Min = -5000.0f;
530500
totalInertiaKgm2 = inertiaKgm2 + transmissionRatio * transmissionRatio * motor.InertiaKgm2;
531501
break;
532502
case AxleDriveType.ForceDriven:
533-
AxleRevolutionsInt.Max = 100.0f;
534-
AxleRevolutionsInt.Min = -100.0f;
535503
totalInertiaKgm2 = inertiaKgm2;
536504
break;
537505
default:
@@ -548,7 +516,7 @@ public Axle(BinaryReader inf) : this()
548516
{
549517
previousSlipPercent = inf.ReadSingle();
550518
previousSlipSpeedMpS = inf.ReadSingle();
551-
axleForceN = inf.ReadSingle();
519+
AxleForceN = inf.ReadSingle();
552520
adhesionK = inf.ReadSingle();
553521
AdhesionConditions = inf.ReadSingle();
554522
frictionN = inf.ReadSingle();
@@ -563,17 +531,17 @@ public void Save(BinaryWriter outf)
563531
{
564532
outf.Write(previousSlipPercent);
565533
outf.Write(previousSlipSpeedMpS);
566-
outf.Write(axleForceN);
534+
outf.Write(AxleForceN);
567535
outf.Write(adhesionK);
568536
outf.Write(AdhesionConditions);
569537
outf.Write(frictionN);
570538
outf.Write(dampingNs);
571539
}
572540

573-
float avgAxleForce;
574-
int times;
575-
576-
public float GetSpeedVariation(float axleSpeedMpS)
541+
/// <summary>
542+
/// Compute variation in axle dynamics. Calculates axle speed, axle angular position and rail force.
543+
/// </summary>
544+
public (float, float, float) GetAxleMotionVariation(float axleSpeedMpS, float axlePositionRad)
577545
{
578546
float axleForceN = AxleWeightN * SlipCharacteristics(axleSpeedMpS - TrainSpeedMpS, TrainSpeedMpS, AdhesionK, AdhesionConditions, Adhesion2);
579547

@@ -600,20 +568,49 @@ public float GetSpeedVariation(float axleSpeedMpS)
600568
frictionalForceN -= Math.Abs(motiveAxleForceN);
601569
}
602570
}
603-
// Assuming Runge-Kutta 4 integration
604-
// Average force computed weighting the four function calls for each iteration
605-
int c = times % 6;
606-
if (c > 0 && c < 5)
571+
return (totalAxleForceN * axleDiameterM * axleDiameterM / 4 / totalInertiaKgm2, axleSpeedMpS * 2 / axleDiameterM, axleForceN);
572+
}
573+
574+
void Integrate(float elapsedClockSeconds)
575+
{
576+
if (elapsedClockSeconds <= 0) return;
577+
float prevSpeedMpS = AxleSpeedMpS;
578+
579+
if (Math.Abs(integratorError) > Math.Max(SlipSpeedMpS, 1) / 1000)
607580
{
608-
avgAxleForce += 2*axleForceN;
609-
times += 2;
581+
++NumOfSubstepsPS;
582+
waitBeforeSpeedingUp = 100;
610583
}
611584
else
612585
{
613-
avgAxleForce += axleForceN;
614-
times++;
586+
if (--waitBeforeSpeedingUp <= 0) //wait for a while before speeding up the integration
587+
{
588+
--NumOfSubstepsPS;
589+
waitBeforeSpeedingUp = 10; //not so fast ;)
590+
}
591+
}
592+
593+
NumOfSubstepsPS = Math.Max(Math.Min(NumOfSubstepsPS, 50), 5);
594+
float dt = elapsedClockSeconds / NumOfSubstepsPS;
595+
float hdt = dt / 2.0f;
596+
float axleForceSumN = 0;
597+
for (int i=0; i<NumOfSubstepsPS; i++)
598+
{
599+
var k1 = GetAxleMotionVariation(AxleSpeedMpS, AxlePositionRad);
600+
var k2 = GetAxleMotionVariation(AxleSpeedMpS + k1.Item1 * hdt, AxlePositionRad + k1.Item2 * hdt);
601+
var k3 = GetAxleMotionVariation(AxleSpeedMpS + k2.Item1 * hdt, AxlePositionRad + k2.Item2 * hdt);
602+
var k4 = GetAxleMotionVariation(AxleSpeedMpS + k3.Item1 * dt, AxlePositionRad + k3.Item2 * dt);
603+
AxleSpeedMpS += (integratorError = (k1.Item1 + 2 * (k2.Item1 + k3.Item1) + k4.Item1) * dt / 6.0f);
604+
AxlePositionRad += (k1.Item2 + 2 * (k2.Item2 + k3.Item2) + k4.Item2) * dt / 6.0f;
605+
axleForceSumN += (k1.Item3 + 2 * (k2.Item3 + k3.Item3) + k4.Item3);
606+
}
607+
AxleForceN = axleForceSumN / (NumOfSubstepsPS * 6);
608+
AxlePositionRad = MathHelper.WrapAngle(AxlePositionRad);
609+
610+
if ((prevSpeedMpS > 0 && AxleSpeedMpS <= 0) || (prevSpeedMpS < 0 && AxleSpeedMpS >= 0))
611+
{
612+
if (Math.Max(brakeRetardForceN, frictionN) > Math.Abs(driveForceN - AxleForceN)) Reset();
615613
}
616-
return totalAxleForceN * axleDiameterM * axleDiameterM / 4 / totalInertiaKgm2;
617614
}
618615

619616
/// <summary>
@@ -625,29 +622,19 @@ public float GetSpeedVariation(float axleSpeedMpS)
625622
/// <param name="timeSpan"></param>
626623
public virtual void Update(float timeSpan)
627624
{
628-
float prevSpeedMpS = axleSpeedMpS;
629-
times = 0;
630-
avgAxleForce = 0;
631-
axleSpeedMpS = AxleRevolutionsInt.Integrate(timeSpan, GetSpeedVariation); //GetSpeedVariation(axleSpeedMpS) * timeSpan;
632-
if (times > 0) axleForceN = avgAxleForce / times;
633-
// TODO: around zero wheel speed calculations become unstable
634-
// Near-zero regime will probably need further corrections
635-
if ((prevSpeedMpS > 0 && axleSpeedMpS <= 0) || (prevSpeedMpS < 0 && axleSpeedMpS >= 0))
636-
{
637-
if (Math.Max(brakeRetardForceN, frictionN) > Math.Abs(driveForceN-axleForceN)) Reset();
638-
}
625+
Integrate(timeSpan);
639626
// TODO: We should calculate brake force here
640627
// Adding and substracting the brake force is correct for normal operation,
641628
// but during wheelslip this will produce wrong results.
642629
// The Axle module subtracts brake force from the motive force for calculation purposes. However brake force is already taken into account in the braking module.
643630
// And thus there is a duplication of the braking effect in OR. To compensate for this, after the slip characteristics have been calculated, the output of the axle module
644631
// has the brake force "added" back in to give the appropriate motive force output for the locomotive. Braking force is handled separately.
645632
// Hence CompensatedAxleForce is the actual output force on the axle.
646-
CompensatedAxleForceN = axleForceN + Math.Sign(TrainSpeedMpS) * brakeRetardForceN;
633+
CompensatedAxleForceN = AxleForceN + Math.Sign(TrainSpeedMpS) * brakeRetardForceN;
647634

648635
if (driveType == AxleDriveType.MotorDriven)
649636
{
650-
motor.RevolutionsRad = axleSpeedMpS * 2.0f * transmissionRatio / (axleDiameterM);
637+
motor.RevolutionsRad = AxleSpeedMpS * 2.0f * transmissionRatio / (axleDiameterM);
651638
motor.Update(timeSpan);
652639
}
653640
if (timeSpan > 0.0f)
@@ -665,8 +652,7 @@ public virtual void Update(float timeSpan)
665652
/// </summary>
666653
public void Reset()
667654
{
668-
axleSpeedMpS = 0;
669-
AxleRevolutionsInt.Reset();
655+
AxleSpeedMpS = 0;
670656
adhesionK = adhesionK_orig;
671657
if (motor != null)
672658
motor.Reset();
@@ -679,10 +665,7 @@ public void Reset()
679665
/// <param name="initValue">Initial condition</param>
680666
public void Reset(double resetTime, float initValue)
681667
{
682-
axleSpeedMpS = initValue;
683-
AxleRevolutionsInt.InitialCondition = initValue;
684-
AxleRevolutionsInt.Reset();
685-
AxleRevolutionsInt.InitialCondition = 0;
668+
AxleSpeedMpS = initValue;
686669
ResetTime = resetTime;
687670
if (motor != null)
688671
motor.Reset();

0 commit comments

Comments
 (0)