Skip to content

Commit d698ad9

Browse files
authored
Merge pull request #41 from AhmedAredah/main
feat: speed-dependent braking model, blended braking fix, and GUI improvements
2 parents 15d1d51 + a7fdda5 commit d698ad9

13 files changed

Lines changed: 201 additions & 38 deletions

File tree

CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ if(BUILD_GUI)
151151
elseif(APPLE)
152152
set(KDREPORTS_DIR "/usr/local/KDAB/KDReports-2.3.95/lib/cmake/KDReports-qt6" CACHE PATH "Path to KDReports CMake directory")
153153
elseif(UNIX)
154-
set(KDREPORTS_DIR "/usr/local/KDAB/KDReports-2.3.95/lib/cmake/KDReports-qt6" CACHE PATH "Path to KDReports CMake directory")
154+
set(KDREPORTS_DIR "/usr/local/${CMAKE_BUILD_TYPE}/lib/cmake/KDReports-qt6" CACHE PATH "Path to KDReports CMake directory")
155155
endif()
156156

157157
# Use the KDReports CMake package
@@ -170,7 +170,7 @@ if(BUILD_SERVER)
170170
elseif(UNIX)
171171
# Linux-specific paths
172172
set(CONTAINER_SEARCH_PATHS "/usr/local/lib/cmake/Container" CACHE PATH "Default path to container's library on Linux")
173-
set(RABBITMQ_CMAKE_DIR "/usr/local/lib/cmake/rabbitmq-c" CACHE PATH "Default path to RabbitMQ-C library on Linux")
173+
set(RABBITMQ_CMAKE_DIR "/usr/local/${CMAKE_BUILD_TYPE}/lib/cmake/rabbitmq-c" CACHE PATH "Default path to RabbitMQ-C library on Linux")
174174
else()
175175
message(FATAL_ERROR "Unsupported platform. Please set paths for CONTAINER_CMAKE_DIR and RABBITMQ_CMAKE_DIR manually.")
176176
endif()

src/NeTrainSim/traindefinition/car.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@ Car::Car(double carLength_m, double carDragCoef,
7676

7777
} // end else if
7878

79+
if (this->carType == TrainTypes::CarType::cargo) {
80+
this->brakedWeightRatio = EC::DefaultCarBrakedWeightRatio_Cargo;
81+
} else {
82+
this->brakedWeightRatio = EC::DefaultCarBrakedWeightRatio_Tender;
83+
}
84+
7985
this->hostLink = std::shared_ptr<NetLink>(); // assign empty placeholder
8086
this->trackCurvature = 0; // zero initialized
8187
this->trackGrade = 0; // zero initialized

src/NeTrainSim/traindefinition/energyconsumption.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,4 +159,16 @@ namespace EC {
159159
return fuelConversionFactor_carTypes[carType];
160160
}
161161

162+
double getBrakeShoeFriction(double speed_mps,
163+
TrainTypes::BrakeShoeType shoeType) {
164+
double v_kmh = speed_mps * 3.6;
165+
switch (shoeType) {
166+
case TrainTypes::BrakeShoeType::composition:
167+
return 0.36 * (v_kmh + 150.0) / (2.0 * v_kmh + 150.0);
168+
case TrainTypes::BrakeShoeType::castIron:
169+
default:
170+
return 0.6 * (v_kmh + 100.0) / (5.0 * v_kmh + 100.0);
171+
}
172+
}
173+
162174
}

src/NeTrainSim/traindefinition/energyconsumption.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,34 @@ namespace EC {
301301
* @returns The fuel conversion factor.
302302
*/
303303
double getFuelConversionFactor(TrainTypes::CarType carType);
304+
305+
/** (Immutable) gravitational acceleration (m/s^2) */
306+
static constexpr double g = 9.8066;
307+
/** (Immutable) default braked weight ratio for locomotives */
308+
static constexpr double DefaultLocomotiveBrakedWeightRatio = 0.9;
309+
/** (Immutable) default braked weight ratio for cargo cars */
310+
static constexpr double DefaultCarBrakedWeightRatio_Cargo = 0.6;
311+
/** (Immutable) default braked weight ratio for tender cars */
312+
static constexpr double DefaultCarBrakedWeightRatio_Tender = 0.7;
313+
314+
/** (Immutable) the speed below which dynamic braking fades to zero (km/h) */
315+
static constexpr double DynamicBrakingFadeSpeed_kmh = 15.0;
316+
317+
/**
318+
* Gets the brake shoe friction coefficient using the Karwatzki model.
319+
*
320+
* @details For cast-iron shoes (UIC standard):
321+
* mu(v) = 0.6 * (v_kmh + 100) / (5 * v_kmh + 100)
322+
* For composition shoes:
323+
* mu(v) = 0.36 * (v_kmh + 150) / (2 * v_kmh + 150)
324+
*
325+
* @param speed_mps Train speed in m/s.
326+
* @param shoeType Type of brake shoe (default: castIron).
327+
* @returns The brake shoe friction coefficient (dimensionless).
328+
*/
329+
double getBrakeShoeFriction(double speed_mps,
330+
TrainTypes::BrakeShoeType shoeType =
331+
TrainTypes::BrakeShoeType::castIron);
304332
}
305333

306334

src/NeTrainSim/traindefinition/locomotive.cpp

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,9 @@ Locomotive::Locomotive(double locomotiveMaxPower_kw,
216216
this->hybridMethod = TrainTypes::LocomotivePowerMethod::notApplicable;
217217
}
218218

219-
};
219+
this->brakedWeightRatio = EC::DefaultLocomotiveBrakedWeightRatio;
220+
221+
};
220222

221223

222224
double Locomotive::getHyperbolicThrottleCoef(double & trainSpeed)
@@ -541,20 +543,24 @@ double Locomotive::getEnergyConsumption(double& LocomotiveVirtualTractivePower,
541543
return EC;
542544
}
543545
else {
544-
// get regenerative eff
546+
// Blended braking: only the dynamic portion (motor as generator) is recoverable.
547+
// Friction braking energy is dissipated as heat.
548+
double recoverablePower =
549+
-this->getRecoverableBrakingPower(tractivePower, trainSpeed);
550+
545551
double regenerativeEff =
546552
this->getRegenerativeEffeciency(tractivePower,
547553
trainAcceleration, trainSpeed);
548554

549-
return ((tractivePower * regenerativeEff + this->auxiliaryPower) *
555+
return ((recoverablePower * regenerativeEff + this->auxiliaryPower) *
550556
EC::getDriveLineEff(trainSpeed,
551557
this->currentLocNotch,
552558
std::abs(powerPortion),
553559
this->powerType,
554560
this->hybridMethod) *
555561
unitConversionFactor);
556562

557-
}
563+
}
558564
}
559565

560566
double Locomotive::getUsedPowerPortion(double trainSpeed,
@@ -568,6 +574,25 @@ double Locomotive::getUsedPowerPortion(double trainSpeed,
568574
return std::min((LocomotiveVirtualTractivePower) / maxPower, 1.0);
569575
}
570576

577+
double Locomotive::getRecoverableBrakingPower(double totalBrakingPower,
578+
double trainSpeed)
579+
{
580+
if (!TrainTypes::locomotiveRechargableTechnologies.exist(this->powerType)) {
581+
return 0.0;
582+
}
583+
584+
// Motor's max dynamic braking power is approximately its rated traction power
585+
double maxDynamicPower = this->maxPower * 1000.0; // kW to W
586+
587+
// Dynamic braking fades linearly below 15 km/h (motor can't generate
588+
// sufficient back-EMF at very low speeds)
589+
double v_kmh = trainSpeed * 3.6;
590+
double fadeFactor = std::min(v_kmh / EC::DynamicBrakingFadeSpeed_kmh, 1.0);
591+
maxDynamicPower *= fadeFactor;
592+
593+
return std::min(std::abs(totalBrakingPower), maxDynamicPower);
594+
}
595+
571596
double Locomotive::getMaxRechargeEnergy(double timeStep, double trainSpeed,
572597
double LocomotiveVirtualTractivePower)
573598
{

src/NeTrainSim/traindefinition/locomotive.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,20 @@ class Locomotive : public TrainComponent{
481481
double getUsedPowerPortion(double trainSpeed,
482482
double LocomotiveVirtualTractivePower);
483483

484+
/**
485+
* @brief Gets the recoverable dynamic braking power, capped at motor capacity.
486+
*
487+
* @details In blended braking, only the dynamic braking portion (motor as generator)
488+
* produces recoverable energy. This is capped at the motor's rated power and
489+
* fades linearly below 15 km/h where dynamic braking becomes ineffective.
490+
*
491+
* @param totalBrakingPower The total braking power magnitude in Watts (positive).
492+
* @param trainSpeed The current train speed in m/s.
493+
* @returns The recoverable dynamic braking power in Watts (positive value).
494+
*/
495+
double getRecoverableBrakingPower(double totalBrakingPower,
496+
double trainSpeed);
497+
484498
void rechargeBatteryByMaxFlow(double timeStep, double trainSpeed,
485499
double powerPortion,
486500
double fuelConversionFactor,

src/NeTrainSim/traindefinition/train.cpp

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,13 @@ Train::Train(
2323
double trainStartTime_sec, double frictionCoeff,
2424
Vector<std::shared_ptr<Locomotive>> locomotives,
2525
Vector<std::shared_ptr<Car>> cars, bool optimize,
26-
double desiredDecelerationRate_mPs,
2726
double operatorReactionTime_s, bool stopIfNoEnergy,
2827
double maxAllowedJerk_mPcs, double optimization_k,
2928
int runOptimizationEvery,
3029
int optimizationLookaheadSteps)
3130
: QObject(nullptr)
3231
{
3332

34-
this->d_des = desiredDecelerationRate_mPs;
3533
this->operatorReactionTime = operatorReactionTime_s;
3634
this->stopTrainIfNoEnergy = stopIfNoEnergy;
3735
this->maxJerk = maxAllowedJerk_mPcs;
@@ -68,7 +66,7 @@ Train::Train(
6866
Map<int, double>()));
6967
Train::NumberOfTrainsInSimulator++;
7068
this->T_s = this->operatorReactionTime
71-
+ (this->totalLength / this->speedOfSound);
69+
+ (this->totalLength / this->brakePipePropagationSpeed);
7270

7371
for (auto &car : this->cars)
7472
{
@@ -173,6 +171,19 @@ double Train::getMinFollowingTrainGap()
173171
return DefaultMinFollowingGap;
174172
}
175173

174+
double Train::getDesiredDeceleration(double speed)
175+
{
176+
double totalBrakingForce = 0.0;
177+
for (auto &vehicle : this->trainVehicles)
178+
{
179+
totalBrakingForce += vehicle->getBrakingForce(speed);
180+
}
181+
double d_physical = totalBrakingForce / this->totalMass;
182+
double d = DefaultServiceBrakingFactor * d_physical;
183+
double d_max = this->coefficientOfFriction * this->g;
184+
return std::max(MinDesiredDeceleration, std::min(d, d_max));
185+
}
186+
176187
double Train::getBatteryEnergyConsumed()
177188
{
178189
double total = 0.0;
@@ -640,15 +651,17 @@ double Train::getSafeGap(double initialGap, double speed,
640651
double gap_lad = 0;
641652
if (!estimate)
642653
{
654+
double d = this->getDesiredDeceleration(speed);
643655
gap_lad = initialGap + T_s * speed
644656
+ (Utils::power(speed, 2)
645-
/ (2.0 * this->d_des));
657+
/ (2.0 * d));
646658
}
647659
else
648660
{
661+
double d = this->getDesiredDeceleration(freeFlowSpeed);
649662
gap_lad = initialGap + T_s * freeFlowSpeed
650663
+ (Utils::power(freeFlowSpeed, 2)
651-
/ (2.0 * this->d_des));
664+
/ (2.0 * d));
652665
};
653666
return gap_lad;
654667
}
@@ -778,11 +791,12 @@ double Train::get_acceleration_an2(
778791
double gap, double minGap, double speed,
779792
double leaderSpeed, double T_s, double frictionCoef)
780793
{
794+
double d = this->getDesiredDeceleration(speed);
781795
double term = 0.0;
782796
term = Utils::power(Utils::power(speed, 2)
783797
- Utils::power(leaderSpeed, 2),
784798
2)
785-
/ (4.0 * this->d_des);
799+
/ (4.0 * d);
786800
term =
787801
term / Utils::power(max((gap - minGap), 0.0001), 2);
788802
return min(term, frictionCoef * this->g);

src/NeTrainSim/traindefinition/train.h

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,12 @@ class NETRAINSIMCORE_EXPORT Train : public QObject {
5353
private:
5454
/** Holds the number of trains in the simulator. */
5555
static unsigned int NumberOfTrainsInSimulator;
56-
/** (Immutable) the default desired deceleration rate */
57-
static constexpr double DefaultDesiredDecelerationRate = 0.2;
56+
/** (Immutable) minimum desired deceleration to prevent infinite safe gaps (m/s^2) */
57+
static constexpr double MinDesiredDeceleration = 0.05;
58+
/** Fraction of emergency braking used for normal service braking.
59+
* 0.5 = 50% of max brake force (Karwatzki physical maximum).
60+
* Typical: 0.35 heavy haul, 0.5 freight, 0.65 passenger. */
61+
static constexpr double DefaultServiceBrakingFactor = 0.5;
5862
/** (Immutable) the default reaction time of the train operator */
5963
static constexpr double DefaultOperatorReactionTime = 1.0;
6064
/** (Immutable) the default switch of the train behaviour if no energy source */
@@ -71,14 +75,12 @@ class NETRAINSIMCORE_EXPORT Train : public QObject {
7175
public:
7276

7377
/**
74-
* (Immutable) the speed of sound in m / s, this is an approximation of the brackes back
75-
* propagation
78+
* (Immutable) brake pipe pressure wave propagation speed in m/s.
79+
* Measured ~250 m/s for service braking on freight (Qiao 2018).
7680
*/
77-
static constexpr double speedOfSound = 343.0;
81+
static constexpr double brakePipePropagationSpeed = 250.0;
7882
/** (Immutable) gravitational acceleration */
7983
const double g = 9.8066;
80-
/** The desired decceleration value */
81-
double d_des;
8284
/** the perception reaction time of the train operator. */
8385
double operatorReactionTime;
8486
/** Total length of the train */
@@ -273,15 +275,12 @@ class NETRAINSIMCORE_EXPORT Train : public QObject {
273275
* @param locomotives The locomotives.
274276
* @param cars The cars.
275277
* @param optimize True to optimize.
276-
* @param desiredDecelerationRate_mPs (Optional) The desired deceleration rate m ps.
277278
* @param operatorReactionTime_s (Optional) The operator reaction time s.
278279
* @param stopIfNoEnergy (Optional) True to stop if no energy.
279-
* @param isRunnigOffGrid (Optional) True if is runnig off grid, false if not.
280280
* @param maxAllowedJerk_mPcs (Optional) The maximum allowed jerk m pcs.
281281
*/
282282
Train(int simulatorID, string id, Vector<int> trainPath, double trainStartTime_sec, double frictionCoeff,
283283
Vector<std::shared_ptr<Locomotive>> locomotives, Vector<std::shared_ptr<Car>> cars, bool optimize,
284-
double desiredDecelerationRate_mPs = DefaultDesiredDecelerationRate,
285284
double operatorReactionTime_s = DefaultOperatorReactionTime,
286285
bool stopIfNoEnergy = DefaultStopIfNoEnergy,
287286
double maxAllowedJerk_mPcs = DefaultMaxAllowedJerk,
@@ -344,6 +343,20 @@ class NETRAINSIMCORE_EXPORT Train : public QObject {
344343
*/
345344
double getMinFollowingTrainGap();
346345

346+
/**
347+
* @brief Gets the speed-dependent desired deceleration rate.
348+
*
349+
* @details Computes comfortable braking deceleration for the
350+
* car-following safe gap calculation. Sums per-vehicle shoe braking
351+
* forces (Karwatzki model), divides by total train mass, then scales
352+
* by DefaultBrakingComfortFactor to convert from physical maximum
353+
* to a desired comfortable rate.
354+
*
355+
* @param speed The current train speed in m/s.
356+
* @returns The desired deceleration in m/s^2.
357+
*/
358+
double getDesiredDeceleration(double speed);
359+
347360
/**
348361
* @brief set the current links the train is spanning
349362
* @param newLinks

src/NeTrainSim/traindefinition/traincomponent.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ double TrainComponent::getResistance(double trainSpeed) {
66
return 0.0;
77
}
88

9+
double TrainComponent::getBrakingForce(double trainSpeed) {
10+
double muShoe = EC::getBrakeShoeFriction(trainSpeed);
11+
double weightKg = this->currentWeight * 1000.0;
12+
return this->brakedWeightRatio * weightKg * EC::g * muShoe;
13+
}
14+
915
void TrainComponent::resetTimeStepConsumptions() {
1016
// reset the energy consumption for the time step.
1117
this->energyConsumed = 0.0;

src/NeTrainSim/traindefinition/traincomponent.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ class TrainComponent : public Battery, public Tank{
4747
int noOfAxiles;
4848
/** Auxiliary power */
4949
double auxiliaryPower;
50+
/** The braked weight ratio for this vehicle (UIC lambda). Set by subclass constructors. */
51+
double brakedWeightRatio = 0.8;
5052

5153
/** The amount of energy consumed in kwh*/
5254
double energyConsumed = 0.0;
@@ -92,6 +94,20 @@ class TrainComponent : public Battery, public Tank{
9294
*/
9395
virtual double getResistance(double trainSpeed);
9496

97+
/**
98+
* \brief Gets the braking force for this vehicle at the given speed.
99+
*
100+
* \details Computes per-vehicle braking force using the Karwatzki model:
101+
* F = brakedWeightRatio * weight * g * mu_shoe(v)
102+
*
103+
* Grade is excluded because it causes discontinuous jumps when vehicles
104+
* cross link boundaries and is already captured in the resistance model.
105+
*
106+
* @param trainSpeed The current train speed in m/s.
107+
* @returns The braking force in Newtons (shoe friction only).
108+
*/
109+
virtual double getBrakingForce(double trainSpeed);
110+
95111
/**
96112
* \brief Resets the energy consumptions data for the current time step
97113
*

0 commit comments

Comments
 (0)