new type Train as struct

development
Martin Scheidt 2022-05-12 16:32:15 +02:00
parent d750da80fb
commit a5868af2c5
12 changed files with 478 additions and 832 deletions

View File

@ -17,6 +17,7 @@ Categories: Added, Changed, Deprecated, Removed, Fixed, and Security.
* renamed TrainRun into TrainRuns
* replaced settings::Dict with type Settings as struct
* replaced path::Dict with type Path as struct
* replaced train::Dict with type Train as struct
* restructured examples/ and data/ in docs/ and test/
* modified test to work with Julia Testsets and with simplier naming of input files
* renamed Validate.jl into types.jl
@ -28,13 +29,14 @@ Categories: Added, Changed, Deprecated, Removed, Fixed, and Security.
* moved createCharacteristicSection() from characteristics.jl to types.jl
* changed title of include files from upper case to lower case
* changed seperation of submodules into a single module with file include
* updated test files to railtoolkit/schema (2022.04)
* updated test files to railtoolkit/schema (2022.05)
### Removed
* dependency Plots
* AdditionalOutput.jl
* EnergySaving.jl
* test/testEnums.jl
* import.jl
## Version [0.8] 2022-01-20

View File

@ -8,6 +8,7 @@ CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
JSONSchema = "7d188eb4-7ad8-530c-ae41-71a32a6d4692"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
YAML = "ddb6d928-2868-570f-bddf-ab3f9cf99eb6"

View File

@ -9,28 +9,32 @@ __precompile__(true)
module TrainRuns
## loading standard library packages
using UUIDs, Dates
using UUIDs, Dates, Statistics
## loading external packages
using YAML, JSONSchema, CSV, DataFrames
export
## Interface
trainrun, Path, Settings, exportToCsv
trainrun, Train, Path, Settings, exportToCsv
## global variables
global g = 9.80665 # acceleration due to gravity (in m/s^2)
global μ = 0.2 # friction as constant, todo: implement as function
global Δv_air = 15.0/3.6 # coefficient for velocitiy difference between train and outdoor air (in m/s)
## include package files
include("types.jl")
include("constructors.jl")
include("formulary.jl")
include("calc.jl")
include("characteristics.jl")
include("behavior.jl")
include("output.jl")
include("import.jl")
include("export.jl")
include("calc.jl")
## main function
"""
trainrun(train::Dict, path::Path, settings::Settings)
trainrun(train::Train, path::Path, settings::Settings)
Calculate the running time of a `train` on a `path`.
The `settings` provides the choice of models for the calculation.
@ -43,15 +47,7 @@ julia> trainrun(train, path)
xxx.xx # in seconds
```
"""
function trainrun(trainInput::Dict, path::Path, settings=Settings()::Settings)
# copy Input data for not changing them
# TODO: or should they be changed? normally it would only make it "better" except for settings.outputDetail == :points_of_interest && isempty(path.poi)
train = copy(trainInput)
# check the input data
train = checkAndSetTrain!(train)
settings.outputDetail == :everything && println("The input has been checked.")
function trainrun(train::Train, path::Path, settings=Settings()::Settings)
# prepare the input data
movingSection = determineCharacteristics(path, train, settings)
settings.outputDetail == :everything && println("The moving section has been prepared.")

View File

@ -5,7 +5,6 @@
# __copyright__ = "2020-2022"
# __license__ = "ISC"
## functions for calculating tractive effort and resisting forces
"""
calculateTractiveEffort(v, tractiveEffortVelocityPairs)
@ -14,19 +13,19 @@ Calculate the trains tractive effort with the `tractiveEffortVelocityPairs` depe
...
# Arguments
- `v::AbstractFloat`: the current velocity in m/s.
- `tractiveEffortVelocityPairs::Array{Array{AbstractFloat,1},1}`: the trains pairs for velocity in m/s and tractive effort in N as one array containing an array for each pair.
- `tractiveEffortVelocityPairs::Array{}`: the trains pairs for velocity in m/s and tractive effort in N as one array containing an array for each pair.
...
# Examples
```julia-repl
julia> calculateTractiveEffort(20.0, [[0.0, 180000], [20.0, 100000], [40.0, 60000], [60.0, 40000], [80.0, 30000]])
julia> calculateTractiveEffort(20.0, [(0.0, 180000), (20.0, 100000), (40.0, 60000), (60.0, 40000), (80.0, 30000)])
100000
julia> calculateTractiveEffort(30.0, [[0.0, 180000], [20.0, 100000], [40.0, 60000], [60.0, 40000], [80.0, 30000]])
julia> calculateTractiveEffort(30.0, [(0.0, 180000), (20.0, 100000), (40.0, 60000), (60.0, 40000), (80.0, 30000)])
80000
```
"""
function calculateTractiveEffort(v::AbstractFloat, tractiveEffortVelocityPairs)
function calculateTractiveEffort(v::AbstractFloat, tractiveEffortVelocityPairs::Array{})
for row in 1:length(tractiveEffortVelocityPairs)
nextPair = tractiveEffortVelocityPairs[row]
if nextPair[1] == v
@ -46,19 +45,19 @@ end #function calculateTractiveEffort
"""
calculate and return the path resistance dependend on the trains position and mass model
"""
function calculatePathResistance(CSs::Vector{Dict}, csId::Integer, s::Real, massModel, train::Dict)
function calculatePathResistance(CSs::Vector{Dict}, csId::Integer, s::Real, massModel, train::Train)
if massModel == :mass_point
pathResistance = calcForceFromCoefficient(CSs[csId][:r_path], train[:m_train])
pathResistance = calcForceFromCoefficient(CSs[csId][:r_path], train.m_train_full)
elseif massModel == :homogeneous_strip
pathResistance = 0.0
s_rear = s - train[:length] # position of the rear of the train
s_rear = s - train.length # position of the rear of the train
while csId > 0 && s_rear < CSs[csId][:s_exit]
pathResistance = pathResistance + (min(s, CSs[csId][:s_exit]) - max(s_rear, CSs[csId][:s_entry])) / train[:length] * calcForceFromCoefficient(CSs[csId][:r_path], train[:m_train])
pathResistance = pathResistance + (min(s, CSs[csId][:s_exit]) - max(s_rear, CSs[csId][:s_entry])) / train.length * calcForceFromCoefficient(CSs[csId][:r_path], train.m_train_full)
csId = csId-1
if csId == 0
# TODO: currently for values < movingSection[:s_entry] the values of movingSection[:s_entry] will be used
return pathResistance + (CSs[1][:s_entry] - s_rear) / train[:length] * calcForceFromCoefficient(CSs[1][:r_path], train[:m_train])
return pathResistance + (CSs[1][:s_entry] - s_rear) / train.length * calcForceFromCoefficient(CSs[1][:r_path], train.m_train_full)
end #if
end #while
end #if
@ -69,7 +68,7 @@ end #function calculatePathResistance
"""
calculate and return tractive and resisting forces for a data point
"""
function calculateForces!(dataPoint::Dict, CSs::Vector{Dict}, csId::Integer, bsType::String, train::Dict, massModel)
function calculateForces!(dataPoint::Dict, CSs::Vector{Dict}, csId::Integer, bsType::String, train::Train, massModel)
# calculate resisting forces
dataPoint[:R_traction] = calcTractionUnitResistance(dataPoint[:v], train)
dataPoint[:R_wagons] = calcWagonsResistance(dataPoint[:v], train)
@ -81,9 +80,9 @@ function calculateForces!(dataPoint::Dict, CSs::Vector{Dict}, csId::Integer, bs
if bsType == "braking" || bsType == "coasting"
dataPoint[:F_T] = 0.0
elseif bsType == "cruising"
dataPoint[:F_T] = min(max(0.0, dataPoint[:F_R]), calculateTractiveEffort(dataPoint[:v], train[:tractiveEffortVelocityPairs]))
dataPoint[:F_T] = min(max(0.0, dataPoint[:F_R]), calculateTractiveEffort(dataPoint[:v], train.tractiveEffort))
else # bsType == "accelerating" || bsType == "diminishing" || 'default'
dataPoint[:F_T] = calculateTractiveEffort(dataPoint[:v], train[:tractiveEffortVelocityPairs])
dataPoint[:F_T] = calculateTractiveEffort(dataPoint[:v], train.tractiveEffort)
end
return dataPoint
@ -191,7 +190,7 @@ end #function getNextPointOfInterest
## This function calculates the data points of the breakFree section.
# Therefore it gets its first data point and the characteristic section and returns the characteristic section including the behavior section for breakFree if needed.
# Info: currently the values of the breakFree section will be calculated like in the accelerating section
function addBreakFreeSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlags::Dict, settings::Settings, train::Dict, CSs::Vector{Dict})
function addBreakFreeSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlags::Dict, settings::Settings, train::Train, CSs::Vector{Dict})
# conditions for the break free section
endOfCSReached = drivingCourse[end][:s] >= CS[:s_exit] || stateFlags[:endOfCSReached]
trainIsHalting = drivingCourse[end][:v] == 0.0
@ -243,7 +242,7 @@ function addBreakFreeSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlags:
if haskey(stateFlags, :usedForDefiningCharacteristics) && stateFlags[:usedForDefiningCharacteristics]
s_braking = 0.0
else
s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train[:a_braking])
s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train.a_braking)
end
# reset state flags
@ -260,16 +259,16 @@ end #function addBreakFreeSection!
## This function calculates the data points of the clearing section.
# Therefore it gets its previous driving course and the characteristic section and returns the characteristic section and driving course including the clearing section.
function addClearingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlags::Dict, settings::Settings, train::Dict, CSs::Vector{Dict})
function addClearingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlags::Dict, settings::Settings, train::Train, CSs::Vector{Dict})
if stateFlags[:previousSpeedLimitReached]
currentSpeedLimit = getCurrentSpeedLimit(CSs, CS[:id], drivingCourse[end][:s], train[:length])
currentSpeedLimit = getCurrentSpeedLimit(CSs, CS[:id], drivingCourse[end][:s], train.length)
if haskey(stateFlags, :usedForDefiningCharacteristics) && stateFlags[:usedForDefiningCharacteristics]
ignoreBraking = true
s_braking = 0.0
else
ignoreBraking = false
s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train[:a_braking])
s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train.a_braking)
end
s_clearing = min(CS[:s_exit]-drivingCourse[end][:s]-s_braking, currentSpeedLimit[:s_end] - drivingCourse[end][:s])
@ -282,7 +281,7 @@ function addClearingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlags::
error("ERROR: clearing <=0.0 although it has to be >0.0 in CS ",CS[:id])
end
#stateFlags[:previousSpeedLimitReached] = false
currentSpeedLimit = getCurrentSpeedLimit(CSs, CS[:id], drivingCourse[end][:s], train[:length])
currentSpeedLimit = getCurrentSpeedLimit(CSs, CS[:id], drivingCourse[end][:s], train.length)
stateFlags[:previousSpeedLimitReached] = currentSpeedLimit[:v] != CS[:v_limit] && drivingCourse[end][:v] >= currentSpeedLimit[:v]
else
stateFlags[:error] = true
@ -293,9 +292,9 @@ end #function addClearingSection
## This function calculates the data points of the accelerating section.
# Therefore it gets its previous driving course and the characteristic section and returns the characteristic section and driving course including the accelerating section
function addAcceleratingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlags::Dict, settings::Settings, train::Dict, CSs::Vector{Dict})
#function addAcceleratingSection!(CS::Dict, drivingCourse::Vector{Dict}, settings::Settings, train::Dict, CSs::Vector{Dict}, ignoreBraking::Bool)
#=if drivingCourse would also be part of movingSectiong: function addAcceleratingSection!(movingSection::Dict, stateFlags::Dict, csId::Integer, settings::Settings, train::Dict)
function addAcceleratingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlags::Dict, settings::Settings, train::Train, CSs::Vector{Dict})
#function addAcceleratingSection!(CS::Dict, drivingCourse::Vector{Dict}, settings::Settings, train::Train, CSs::Vector{Dict}, ignoreBraking::Bool)
#=if drivingCourse would also be part of movingSectiong: function addAcceleratingSection!(movingSection::Dict, stateFlags::Dict, csId::Integer, settings::Settings, train::Train)
CSs = movingSection[:characteristicSections]
CS = CSs[csId]
drivingCourse = movingSection[:drivingCourse]=#
@ -307,7 +306,7 @@ function addAcceleratingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFla
s_braking = 0.0
else
ignoreBraking = false
s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train[:a_braking])
s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train.a_braking)
end
# conditions for the accelerating section
@ -322,7 +321,7 @@ function addAcceleratingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFla
BS = createBehaviorSection("accelerating", drivingCourse[end][:s], drivingCourse[end][:v], drivingCourse[end][:i])
drivingCourse[end][:behavior] = BS[:type]
currentSpeedLimit = getCurrentSpeedLimit(CSs, CS[:id], drivingCourse[end][:s], train[:length])
currentSpeedLimit = getCurrentSpeedLimit(CSs, CS[:id], drivingCourse[end][:s], train.length)
previousSpeedLimitReached = currentSpeedLimit[:v] != CS[:v_limit] && drivingCourse[end][:v] >= currentSpeedLimit[:v]
speedLimitReached = drivingCourse[end][:v] >= CS[:v_limit]
#speedLimitReached = drivingCourse[end][:v] > currentSpeedLimit[:v]
@ -334,18 +333,18 @@ function addAcceleratingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFla
for cycle in 1:settings.approxLevel+1 # first cycle with normal step size followed by cycles with reduced step size depending on the level of approximation
if !ignoreBraking
s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train[:a_braking])
s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train.a_braking)
end
while !targetSpeedReached && !speedLimitReached && !brakingStartReached && !pointOfInterestReached && tractionSurplus && !previousSpeedLimitReached
# 03/08 old: while drivingCourse[end][:v] < CS[:v_peak] && drivingCourse[end][:v] <= currentSpeedLimit[:v] && !brakingStartReached && drivingCourse[end][:s] < nextPointOfInterest[1] && drivingCourse[end][:F_T] > drivingCourse[end][:F_R] # as long as s_i + s_braking < s_CSexit
if drivingCourse[end][:s] >= currentSpeedLimit[:s_end]
# could be asked after creating an data point. This way here prevents even a minimal exceedance of speed limit will be noticed. On the other hand the train cruises possibly a little to long
currentSpeedLimit = getCurrentSpeedLimit(CSs, CS[:id], drivingCourse[end][:s], train[:length])
currentSpeedLimit = getCurrentSpeedLimit(CSs, CS[:id], drivingCourse[end][:s], train.length)
end
# acceleration (in m/s^2):
drivingCourse[end][:a] = calcAcceleration(drivingCourse[end][:F_T], drivingCourse[end][:F_R], train[:m_train], train[:ξ_train])
drivingCourse[end][:a] = calcAcceleration(drivingCourse[end][:F_T], drivingCourse[end][:F_R], train.m_train_full, train.ξ_train)
# create the next data point
push!(drivingCourse, moveAStep(drivingCourse[end], settings.stepVariable, currentStepSize, CS[:id]))
@ -356,7 +355,7 @@ function addAcceleratingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFla
# conditions for the next while cycle
if !ignoreBraking
s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train[:a_braking])
s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train.a_braking)
end
brakingStartReached = drivingCourse[end][:s] +s_braking >= CS[:s_exit]
speedLimitReached = drivingCourse[end][:v] > CS[:v_limit]
@ -539,7 +538,7 @@ end #function addAcceleratingSection!
## This function calculates the data points of the cruising section.
# Therefore it gets its first data point and the characteristic section and returns the characteristic section including the behavior section for cruising if needed.
function addCruisingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlags::Dict, s_cruising::Real, settings::Settings, train::Dict, CSs::Vector{Dict}, cruisingType::String)
function addCruisingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlags::Dict, s_cruising::Real, settings::Settings, train::Train, CSs::Vector{Dict}, cruisingType::String)
trainIsClearing = cruisingType == "clearing"
trainIsBrakingDownhill = cruisingType == "downhillBraking"
@ -555,11 +554,11 @@ function addCruisingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlags::
s_braking = 0.0
else
ignoreBraking = false
s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train[:a_braking])
s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train.a_braking)
end
# conditions for cruising section
#s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train[:a_braking])
#s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train.a_braking)
brakingStartReached = drivingCourse[end][:s] + s_braking >= CS[:s_exit] || stateFlags[:brakingStartReached]
speedIsValid = drivingCourse[end][:v]>0.0 && drivingCourse[end][:v]<=CS[:v_peak]
tractionDeficit = drivingCourse[end][:F_T] < drivingCourse[end][:F_R]
@ -584,7 +583,7 @@ function addCruisingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlags::
if settings.massModel == :homogeneous_strip && CS[:id] > 1
# conditions for cruising section
trainInPreviousCS = drivingCourse[end][:s] < CS[:s_entry] + train[:length]
trainInPreviousCS = drivingCourse[end][:s] < CS[:s_entry] + train.length
targetPositionReached = drivingCourse[end][:s] >= BS[:s_entry] +s_cruising
resistingForceNegative = drivingCourse[end][:F_R] < 0.0
# targetSpeedReached = stateFlags[:speedLimitReached] || drivingCourse[end][:v] >= CS[:v_peak]
@ -598,7 +597,7 @@ function addCruisingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlags::
for cycle in 1:settings.approxLevel+1 # first cycle with normal step size followed by cycles with reduced step size depending on the level of approximation
while trainInPreviousCS && !targetPositionReached && !pointOfInterestReached && !tractionDeficit && (trainIsClearing || (trainIsBrakingDownhill == resistingForceNegative)) # while clearing tractive or braking force can be used
# 03/09 old: while drivingCourse[end][:s] < CS[:s_entry] + train[:length] && drivingCourse[end][:s] < BS[:s_entry] +s_cruising && drivingCourse[end][:s] < nextPointOfInterest[1] && drivingCourse[end][:F_T]>=drivingCourse[end][:F_R]
# 03/09 old: while drivingCourse[end][:s] < CS[:s_entry] + train.length && drivingCourse[end][:s] < BS[:s_entry] +s_cruising && drivingCourse[end][:s] < nextPointOfInterest[1] && drivingCourse[end][:F_T]>=drivingCourse[end][:F_R]
# the tractive effort is lower than the resisiting forces and the train has use the highest possible effort to try to stay at v_peak OR the mass model homogeneous strip is used and parts of the train are still in former CS
#TODO: maybe just consider former CS with different path resistance?
# tractive effort (in N):
@ -617,7 +616,7 @@ function addCruisingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlags::
if settings.stepVariable == :distance || settings.stepVariable == time
push!(drivingCourse, moveAStep(drivingCourse[end], settings.stepVariable, currentStepSize, CS[:id]))
else
push!(drivingCourse, moveAStep(drivingCourse[end], position, train[:length]/(10.0^cycle), CS[:id])) # TODO which step size should be used?
push!(drivingCourse, moveAStep(drivingCourse[end], position, train.length/(10.0^cycle), CS[:id])) # TODO which step size should be used?
end
drivingCourse[end][:behavior] = BS[:type]
push!(BS[:dataPoints], drivingCourse[end][:i])
@ -635,7 +634,7 @@ function addCruisingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlags::
pointOfInterestReached = drivingCourse[end][:s] >= nextPointOfInterest[1] # POIs include s_exit as well
tractionDeficit = drivingCourse[end][:F_T] < drivingCourse[end][:F_R]
targetPositionReached = drivingCourse[end][:s] >= BS[:s_entry] +s_cruising
trainInPreviousCS = drivingCourse[end][:s] < CS[:s_entry] + train[:length]
trainInPreviousCS = drivingCourse[end][:s] < CS[:s_entry] + train.length
resistingForceNegative = drivingCourse[end][:F_R] < 0.0
end #while
@ -657,7 +656,7 @@ function addCruisingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlags::
currentStepSize = settings.stepSize / 10.0^cycle
end
elseif drivingCourse[end][:s] > BS[:s_entry] + s_cruising # TODO also the following? drivingCourse[end][:s] > CSs[CS[:id]][:s_entry] + train[:length]))
elseif drivingCourse[end][:s] > BS[:s_entry] + s_cruising # TODO also the following? drivingCourse[end][:s] > CSs[CS[:id]][:s_entry] + train.length))
if settings.stepVariable == :distance
currentStepSize=BS[:s_entry] + s_cruising-drivingCourse[end-1][:s]
else
@ -667,7 +666,7 @@ function addCruisingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlags::
elseif drivingCourse[end][:s] == BS[:s_entry] + s_cruising # || drivingCourse[end][:s]==CS[:s_exit]
break
elseif drivingCourse[end][:s] >= CS[:s_entry] + train[:length]
elseif drivingCourse[end][:s] >= CS[:s_entry] + train.length
break
elseif drivingCourse[end][:s] == nextPointOfInterest[1]
@ -782,12 +781,12 @@ function addCruisingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlags::
# set state flags
stateFlags[:endOfCSReached] = drivingCourse[end][:s] == CS[:s_exit]
if !ignoreBraking
s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train[:a_braking])
s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train.a_braking)
end
stateFlags[:brakingStartReached] = brakingStartReached || drivingCourse[end][:s] + s_braking >= CS[:s_exit]
stateFlags[:tractionDeficit] = tractionDeficit
stateFlags[:resistingForceNegative] = drivingCourse[end][:F_R] < 0.0
currentSpeedLimit = getCurrentSpeedLimit(CSs, CS[:id], drivingCourse[end][:s], train[:length])
currentSpeedLimit = getCurrentSpeedLimit(CSs, CS[:id], drivingCourse[end][:s], train.length)
stateFlags[:previousSpeedLimitReached] = currentSpeedLimit[:v] != CS[:v_limit] && drivingCourse[end][:v] >= currentSpeedLimit[:v]
stateFlags[:error] = !(targetPositionReached || tractionDeficit || !(cruisingType == "clearing" || ((cruisingType == "downhillBraking") == resistingForceNegative)))
@ -796,7 +795,7 @@ end #function addCruisingSection!
## This function calculates the data points for diminishing run when using maximum tractive effort and still getting slower
function addDiminishingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlags::Dict, settings::Settings, train::Dict, CSs::Vector{Dict})
function addDiminishingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlags::Dict, settings::Settings, train::Train, CSs::Vector{Dict})
calculateForces!(drivingCourse[end], CSs, CS[:id], "diminishing", train, settings.massModel)
if haskey(stateFlags, :usedForDefiningCharacteristics) && stateFlags[:usedForDefiningCharacteristics]
@ -804,14 +803,14 @@ function addDiminishingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlag
s_braking = 0.0
else
ignoreBraking = false
s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train[:a_braking])
s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train.a_braking)
end
# conditions for diminishing section
targetSpeedReached = drivingCourse[end][:v] <= 0.0
endOfCSReached = drivingCourse[end][:s] >= CS[:s_exit] || stateFlags[:endOfCSReached]
tractionDeficit = drivingCourse[end][:F_T] < drivingCourse[end][:F_R] #|| stateFlags[:tractionDeficit]
#s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train[:a_braking])
#s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train.a_braking)
brakingStartReached = drivingCourse[end][:s] + s_braking >= CS[:s_exit] || stateFlags[:brakingStartReached]
# use the conditions for the diminishing section
@ -828,7 +827,7 @@ function addDiminishingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlag
while tractionDeficit && !brakingStartReached && !pointOfInterestReached && !targetSpeedReached
# 03/09 old: while drivingCourse[end][:F_T] < drivingCourse[end][:F_R] && !brakingStartReached && drivingCourse[end][:s] < nextPointOfInterest[1] && drivingCourse[end][:v]>0.0 # as long as s_i + s_braking < s_end
# acceleration (in m/s^2):
drivingCourse[end][:a] = calcAcceleration(drivingCourse[end][:F_T], drivingCourse[end][:F_R], train[:m_train], train[:ξ_train])
drivingCourse[end][:a] = calcAcceleration(drivingCourse[end][:F_T], drivingCourse[end][:F_R], train.m_train_full, train.ξ_train)
# create the next data point
push!(drivingCourse, moveAStep(drivingCourse[end], settings.stepVariable, currentStepSize, CS[:id]))
@ -839,7 +838,7 @@ function addDiminishingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlag
# conditions for the next while cycle
if !ignoreBraking
s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train[:a_braking])
s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train.a_braking)
end
brakingStartReached = drivingCourse[end][:s] +s_braking >= CS[:s_exit]
pointOfInterestReached = drivingCourse[end][:s] >= nextPointOfInterest[1]
@ -983,7 +982,7 @@ end #function addDiminishingSection!
## This function calculates the data points of the coasting section.
# Therefore it gets its previous driving course and the characteristic section and returns the characteristic section and driving course including the coasting section
function addCoastingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlags::Dict, settings::Settings, train::Dict, CSs::Vector{Dict})
function addCoastingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlags::Dict, settings::Settings, train::Train, CSs::Vector{Dict})
# TODO: if the rear of the train is still located in a former characteristic section it has to be checked if its speed limit can be kept
# with getCurrentSpeedLimit
@ -991,7 +990,7 @@ function addCoastingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlags::
targetSpeedReached = drivingCourse[end][:v] <= CS[:v_exit]
endOfCSReached = drivingCourse[end][:s] >= CS[:s_exit] || stateFlags[:endOfCSReached]
s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train[:a_braking])
s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train.a_braking)
brakingStartReached = drivingCourse[end][:s] + s_braking >= CS[:s_exit] || stateFlags[:brakingStartReached]
# use the conditions for the coasting section
@ -1011,7 +1010,7 @@ function addCoastingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlags::
calculateForces!(drivingCourse[end], CSs, CS[:id], BS[:type], train, settings.massModel)
# acceleration (in m/s^2):
drivingCourse[end][:a] = calcAcceleration(drivingCourse[end][:F_T], drivingCourse[end][:F_R], train[:m_train], train[:ξ_train])
drivingCourse[end][:a] = calcAcceleration(drivingCourse[end][:F_T], drivingCourse[end][:F_R], train.m_train_full, train.ξ_train)
# create the next data point
push!(drivingCourse, moveAStep(drivingCourse[end], settings.stepVariable, currentStepSize, CS[:id]))
@ -1019,7 +1018,7 @@ function addCoastingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlags::
push!(BS[:dataPoints], drivingCourse[end][:i])
# conditions for the next while cycle
s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train[:a_braking])
s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train.a_braking)
brakingStartReached = drivingCourse[end][:s] + s_braking >= CS[:s_exit]
pointOfInterestReached = drivingCourse[end][:s] >= nextPointOfInterest[1]
targetSpeedReached = drivingCourse[end][:v] <= CS[:v_exit] || drivingCourse[end][:v] > CS[:v_peak]
@ -1146,7 +1145,7 @@ end #function addCoastingSection!
## This function calculates the data points of the braking section.
# Therefore it gets its first data point and the characteristic section and returns the characteristic section including the behavior section for braking if needed.
function addBrakingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlags::Dict, settings::Settings, train::Dict, CSs::Vector{Dict})
function addBrakingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlags::Dict, settings::Settings, train::Train, CSs::Vector{Dict})
# conditions for braking section
targetSpeedReached = drivingCourse[end][:v] <= CS[:v_exit]
endOfCSReached = drivingCourse[end][:s] >= CS[:s_exit] || stateFlags[:endOfCSReached]
@ -1168,7 +1167,7 @@ function addBrakingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlags::D
calculateForces!(drivingCourse[end], CSs, CS[:id], BS[:type], train, settings.massModel)
# acceleration (in m/s^2):
drivingCourse[end][:a] = train[:a_braking]
drivingCourse[end][:a] = train.a_braking
# TODO or: drivingCourse[end][:a] = calcBrakingAcceleration(drivingCourse[end][:v], CS[:v_exit], CS[:s_exit]-drivingCourse[end][:s])
if settings.stepVariable == :distance && ((drivingCourse[end][:v]/drivingCourse[end][:a])^2+2*currentStepSize/drivingCourse[end][:a])<0.0 || (drivingCourse[end][:v]^2+2*currentStepSize*drivingCourse[end][:a])<0.0
@ -1289,7 +1288,7 @@ function addBrakingSection!(CS::Dict, drivingCourse::Vector{Dict}, stateFlags::D
end # else: return the characteristic section without a braking section
# set state flags
currentSpeedLimit = getCurrentSpeedLimit(CSs, CS[:id], drivingCourse[end][:s], train[:length])
currentSpeedLimit = getCurrentSpeedLimit(CSs, CS[:id], drivingCourse[end][:s], train.length)
stateFlags[:previousSpeedLimitReached] = currentSpeedLimit[:v] != CS[:v_limit] && drivingCourse[end][:v] >= currentSpeedLimit[:v]
stateFlags[:speedLimitReached] = drivingCourse[end][:v] >= CS[:v_exit]
stateFlags[:endOfCSReached] = endOfCSReached
@ -1303,7 +1302,7 @@ end #function addBrakingSection!
## This function calculates the data point of the standstill.
# Therefore it gets its first data point and the characteristic section and returns the characteristic section including the standstill if needed.
function addStandstill!(CS::Dict, drivingCourse::Vector{Dict}, settings::Settings, train::Dict, CSs::Vector{Dict})
function addStandstill!(CS::Dict, drivingCourse::Vector{Dict}, settings::Settings, train::Train, CSs::Vector{Dict})
if drivingCourse[end][:v] == 0.0
BS = createBehaviorSection("standstill", drivingCourse[end][:s], drivingCourse[end][:v], drivingCourse[end][:i])
merge!(BS, Dict(:length => 0.0, # total length (in m)
@ -1347,8 +1346,8 @@ function recalculateLastBrakingPoint!(drivingCourse, s_target, v_target)
# calculate other values
previousPoint[:a] = calcBrakingAcceleration(previousPoint[:v], currentPoint[:v], currentPoint[:Δs])
# # TODO: just for testing
# if previousPoint[:a]<train[:a_braking] || previousPoint[:a]>=0.0
# println("Warning: a_braking gets to high in CS ",CS[:id], " with a=",previousPoint[:a] ," > ",train[:a_braking])
# if previousPoint[:a]<train.a_braking || previousPoint[:a]>=0.0
# println("Warning: a_braking gets to high in CS ",CS[:id], " with a=",previousPoint[:a] ," > ",train.a_braking)
# end
currentPoint[:Δt] = calc_Δt_with_Δv(currentPoint[:Δv], previousPoint[:a]) # step size (in s)
currentPoint[:t] = previousPoint[:t] + currentPoint[:Δt] # point in time (in s)

View File

@ -8,7 +8,7 @@
# Calculate the running time of a train run on a path with special settings with information from the corresponding YAML files with the file paths `trainDirectory`, `pathDirectory`, `settingsDirectory`.
# calculate a train run focussing on using the minimum possible running time
function calculateMinimumRunningTime!(movingSection::Dict, settings::Settings, train::Dict)
function calculateMinimumRunningTime!(movingSection::Dict, settings::Settings, train::Train)
CSs::Vector{Dict} = movingSection[:characteristicSections]
if settings.massModel == :homogeneous_strip && settings.stepVariable == speed
@ -32,7 +32,7 @@ function calculateMinimumRunningTime!(movingSection::Dict, settings::Settings, t
end
# determine the different flags for switching between the states for creatinge moving phases
s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train[:a_braking])
s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train.a_braking)
calculateForces!(drivingCourse[end], CSs, CS[:id], "default", train, settings.massModel) # tractive effort and resisting forces (in N)
previousSpeedLimitReached = false
@ -64,12 +64,12 @@ function calculateMinimumRunningTime!(movingSection::Dict, settings::Settings, t
elseif settings.stepVariable == time
s_cruising = calc_Δs_with_Δt(settings.stepSize, drivingCourse[end][:a], drivingCourse[end][:v])
elseif settings.stepVariable == velocity
s_cruising = train[:length]/(10.0) # TODO which step size should be used?
s_cruising = train.length/(10.0) # TODO which step size should be used?
end
(CS, drivingCourse, stateFlags) = addCruisingSection!(CS, drivingCourse, stateFlags, s_cruising, settings, train, CSs, "cruising")
elseif drivingCourse[end][:F_R] < 0 && stateFlags[:speedLimitReached]
s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train[:a_braking])
s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train.a_braking)
s_cruising = CS[:s_exit] - drivingCourse[end][:s] - s_braking
if s_cruising > 0.0
@ -79,7 +79,7 @@ function calculateMinimumRunningTime!(movingSection::Dict, settings::Settings, t
end
elseif drivingCourse[end][:F_T] == drivingCourse[end][:F_R] || stateFlags[:speedLimitReached]
s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train[:a_braking])
s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train.a_braking)
s_cruising = CS[:s_exit] - drivingCourse[end][:s] - s_braking
if s_cruising > 0.0 # TODO: define a minimum cruising length?

View File

@ -6,9 +6,9 @@
# __license__ = "ISC"
## create a moving section and its containing characteristic sections with secured braking, accelerating and cruising behavior
function determineCharacteristics(path::Path, train::Dict, settings::Settings)
movingSection = createMovingSection(path, train[:v_limit], train[:length])
movingSection = secureBrakingBehavior!(movingSection, train[:a_braking])
function determineCharacteristics(path::Path, train::Train, settings::Settings)
movingSection = createMovingSection(path, train.v_limit, train.length)
movingSection = secureBrakingBehavior!(movingSection, train.a_braking)
movingSection = secureAcceleratingBehavior!(movingSection, settings, train)
#movingSection = secureCruisingBehavior!(movingSection, settings, train)
@ -45,7 +45,7 @@ function secureBrakingBehavior!(movingSection::Dict, a_braking::Real)
end #function secureBrakingBehavior!
## define the intersection velocities between the characterisitc sections to secure accelerating behavior
function secureAcceleratingBehavior!(movingSection::Dict, settings::Settings, train::Dict)
function secureAcceleratingBehavior!(movingSection::Dict, settings::Settings, train::Train)
# this function limits the entry and exit velocity of the characteristic sections in case that the train accelerates in every section and cruises aterwards
CSs = movingSection[:characteristicSections]
@ -82,7 +82,7 @@ function secureAcceleratingBehavior!(movingSection::Dict, settings::Settings, tr
(CS, acceleratingCourse, stateFlags) = addClearingSection!(CS, acceleratingCourse, stateFlags, settings, train, CSs) # this function is needed in case the train is not allowed to accelerate because of a previous speed limit
end
else
if settings.massModel == :mass_point || acceleratingCourse[end][:s] > CS[:s_entry] + train[:length]
if settings.massModel == :mass_point || acceleratingCourse[end][:s] > CS[:s_entry] + train.length
break
else
(CS, acceleratingCourse, stateFlags) = addDiminishingSection!(CS, acceleratingCourse, stateFlags, settings, train, CSs) # this function is needed in case the resisitng forces are higher than the maximum possible tractive effort
@ -112,7 +112,7 @@ end #function secureAcceleratingBehavior!
#=
## define the intersection velocities between the characterisitc sections to secure cruising behavior
function secureCruisingBehavior!(movingSection::Dict, settings::Settings, train::Dict)
function secureCruisingBehavior!(movingSection::Dict, settings::Settings, train::Train)
# limit the exit velocity of the characteristic sections in case that the train cruises in every section at v_peak
CSs = movingSection[:characteristicSections]
@ -147,7 +147,7 @@ function secureCruisingBehavior!(movingSection::Dict, settings::Settings, train:
(CS, cruisingCourse, stateFlags) = addCruisingSection!(CS, cruisingCourse, stateFlags, s_cruising, settings, train, CSs, "downhillBraking")
end
else
if settings.massModel == :mass_point || cruisingCourse[end][:s] > CS[:s_entry] + train[:length]
if settings.massModel == :mass_point || cruisingCourse[end][:s] > CS[:s_entry] + train.length
break
else
(CS, cruisingCourse, stateFlags) = addDiminishingSection!(CS, cruisingCourse, stateFlags, settings, train, CSs) # this function is needed in case the resisitng forces are higher than the maximum possible tractive effort

View File

@ -1,7 +1,7 @@
#!/usr/bin/env julia
# -*- coding: UTF-8 -*-
# __julia-version__ = 1.7.2
# __author__ = "Martin Scheidt"
# __author__ = "Martin Scheidt, Max Kannenberg"
# __copyright__ = "2022"
# __license__ = "ISC"
@ -77,7 +77,7 @@ function Settings(file="DEFAULT")
settings = Dict()
end
## set the variables if they exist in "settings"
## set the variables in "settings"
haskey(settings, "massModel") ? massModel = Symbol(settings["massModel"]) : nothing
haskey(settings, "stepVariable") ? stepVariable = Symbol(settings["stepVariable"]) : nothing
haskey(settings, "stepSize") ? stepSize = settings["stepSize"] : nothing
@ -89,8 +89,7 @@ function Settings(file="DEFAULT")
Settings(massModel, stepVariable, stepSize, approxLevel, outputDetail, outputFormat, outputDir)
end #function Settings() # constructor
end #function Settings() # outer constructor
"""
Path(file, type = :YAML)
@ -227,7 +226,7 @@ function Path(file, type = :YAML)
end
path = paths[1]
## set the variables if they exist in "settings"
## set the variables in "path"
# required
name = path["name"]
id = path["id"]
@ -275,7 +274,345 @@ function Path(file, type = :YAML)
Path(name, id, uuid, poi, sections)
end #function Path() # constructor
end #function Path() # outer constructor
"""
Train(file, type = :YAML)
Train is a datastruture for calculation context.
The function Train() will create a train to use in calculations.
Supported formats for the YAML files are: railtoolkit/schema (2022.05)
# Example
```jldoctest
julia> my_train = Train("file.yaml") # will generate a train from a YAML file.
Train(variables)
```
"""
function Train(file, type = :YAML)
## default values
name = "" #
id = "" #
uuid = UUIDs.uuid4() #
length = 0 # in meter
m_train_full = 0 # in kilogram
m_train_empty = 0 # in kilogram
m_loco = 0 # in kilogram
m_td = 0 # in kilogram
m_tc = 0 # in kilogram
m_w = 0 # in kilogram
ξ_train = 1.08 # rotation mass factor, source: "Fahrdynamik des Schienenverkehrs" by Wende, 2003, p. 13 for "Zug, überschlägliche Berechnung"
ξ_loco = 1.09 # rotation mass factor
ξ_cars = 1.06 # rotation mass factor
transportType = :freight # "freight" or "passenger" for resistance calculation
v_limit = 140 # in m/s (default 504 km/h)
a_braking = 0 # in m/s^2, todo: implement as function
f_Rtd0 = 0 # coefficient for basic resistance due to the traction units driving axles (in ‰)
f_Rtc0 = 0 # coefficient for basic resistance due to the traction units carring axles (in ‰)
F_Rt2 = 3000 # coefficient for air resistance of the traction units (in N)
f_Rw0 = 0 # coefficient for the consists basic resistance (in ‰)
f_Rw1 = 0 # coefficient for the consists resistance to rolling (in ‰)
f_Rw2 = 0 # coefficient fo the consistsr air resistance (in ‰)
F_v_pairs = [] # [v in m/s, F_T in N]
## load from file
if type == :YAML
data = YAML.load(open(file))
if data["schema"] != "https://railtoolkit.org/schema/rolling-stock.json"
error("Could not load path file '$file'.\n
YAML format is not recognized.
Currently supported: railtoolkit/schema/rolling-stock (2022.05)")
end
if data["schema_version"] != "2022.05"
error("Could not load path file '$file'.\n
YAML format is not recognized.
Currently supported: railtoolkit/schema/rolling-stock (2022.05)")
end
## JSON schema for YAML-file validation
railtoolkit_schema = Schema("""{
"required": [ "schema", "schema_version" ],
"anyOf": [
{"required": [ "trains" ] },
{"required": [ "vehicles" ] }
],
"properties": {
"schema": {
"description": "Identifier of the schema",
"enum": [ "https://railtoolkit.org/schema/rolling-stock.json" ]
},
"schema_version": {
"description": "Version of the schema",
"type": "string",
"pattern": "[2-9][0-9][0-9][0-9].[0-1][0-9]"
},
"trains": {
"type": "array",
"minItems": 1,
"items": {
"required": [ "name", "id", "formation" ],
"type": "object",
"properties": {
"id": {
"description": "Identifier of the train",
"type": "string"
},
"name": {
"description": "Name of the train",
"type": "string"
},
"UUID": {
"description": "The unique identifier for a train",
"type": "string",
"format": "uuid"
},
"formation": {
"description": "Collection of vehicles that form the train",
"type": "array",
"minItems": 1,
"uniqueItems": false,
"items": {
"type": "string"
}
}
}
}
},
"vehicles": {
"type": "array",
"minItems": 1,
"items": {
"required": [ "name", "id", "vehicle_type", "length", "mass" ],
"type": "object",
"properties": {
"air_resistance": {
"description": "coefficient for air resistance in permil",
"type": "number",
"exclusiveMinimum": 0
},
"base_resistance": {
"description": "coefficient for basic resistance in permil",
"type": "number",
"exclusiveMinimum": 0
},
"id": {
"description": "Identifier of the vehicle",
"type": "string"
},
"length": {
"description": "The length of the vehicle in meter",
"type": "number",
"exclusiveMinimum": 0
},
"load_limit": {
"description": "The maximum permitted load of the vehicle in metric ton",
"type": "number",
"exclusiveMinimum": 0
},
"mass_traction": {
"description": "The mass on the powered axles of the vehicle in metric ton",
"type": "number",
"exclusiveMinimum": 0
},
"mass": {
"description": "The empty mass of the vehicle in metric ton",
"type": "number",
"exclusiveMinimum": 0
},
"name": {
"description": "Name of the vehicle",
"type": "string"
},
"picture": {
"description": "A URI with a picture for humans",
"type": "string",
"format": "uri"
},
"power_type": {
"description": "Type of propulsion",
"enum": [ "diesel", "electric", "steam" ]
},
"rolling_resistance": {
"description": "coefficient for resistance of rolling axles in permil",
"type": "number",
"exclusiveMinimum": 0
},
"rotation_mass": {
"description": "Factor for rotating mass; >= 1",
"type": "number",
"minimum": 1
},
"speed_limit": {
"description": "Maximum permitted speed in kilometers per hour",
"type": "number",
"exclusiveMinimum": 0
},
"tractive_effort": {
"description": "Tractive effort as pairs of speed in kilometers per hour and tractive force in newton",
"type": "array",
"minItems": 3,
"uniqueItems": true,
"items": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"uniqueItems": true,
"items": {
"type": "number",
"minimum": 0
}
}
},
"UUID": {
"description": "The unique identifier for a vehicle",
"type": "string",
"format": "uuid"
},
"vehicle_type": {
"description": "Type of vehicle",
"enum": [ "traction unit", "freight", "passenger", "multiple unit" ]
}
}
}
}
}
}""")
try
validate(railtoolkit_schema, data)
catch err
error("Could not load path file '$file'.\n
YAML format is not recognized.
Currently supported: railtoolkit/schema/rolling-stock (2022.05)")
end
else
error("Unknown file type '$type'")
end #if type
trains = data["trains"]
Base.length(trains) > 1 ? println("WARNING: the loaded file contains more than one train. Using only the first!") : nothing
Base.length(trains) == 0 ? error("No train present in file '$file'") : nothing
train = trains[1]
used_vehicles = unique(train["formation"])
included_vehicles = []
for vehicle in data["vehicles"]
push!(included_vehicles,vehicle["id"])
end
## test if all vehicles of the formation are avilable
for vehicle in used_vehicles
vehicle included_vehicles ? error("'$vehicle' is not present in '$file'") : nothing
end
## gather the count of vehicles and usage in the formation
vehicles = NamedTuple[]
for vehicle in data["vehicles"]
if vehicle["id"] in used_vehicles
n = count(==(vehicle["id"]),train["formation"])
type = vehicle["vehicle_type"]
type == "traction unit" || type == "multiple unit" ? propulsion = true : propulsion = false
type == "passenger" || type == "multiple unit" ? transportType = :passenger : nothing
push!(vehicles, (data=vehicle, n=n, propulsion=propulsion) )
end
end
## set the variables in "train"
name = train["name"]
id = train["id"]
haskey(train, "UUID") ? uuid = parse(UUID, train["UUID"] ) : nothing
transportType == :freight ? a_braking = -0.225 : a_braking = -0.375 # set a default a_braking value depending on the train type
## set the variables for all vehicles
for vehicle in vehicles
length += vehicle.data["length"] * vehicle.n
m_train_full += vehicle.data["mass"] * vehicle.n * 1000 # in kg
m_train_empty += vehicle.data["mass"] * vehicle.n * 1000 # in kg
haskey(vehicle.data, "load_limit") ?
m_train_full += vehicle.data["load_limit"] * vehicle.n * 1000 : # in kg
nothing
haskey(vehicle.data, "speed_limit") ?
v_limit > vehicle.data["speed_limit"]/3.6 ? v_limit = vehicle.data["speed_limit"]/3.6 : nothing :
nothing
end
## divide vehicles in propulsion and non-propulsion
loco = []
for i in 1:Base.length(vehicles)
if vehicles[i].propulsion
push!(loco, vehicles[i])
deleteat!(vehicles, i)
end
end
Base.length(loco) > 1 ? println("WARNING: the loaded file contains more than one traction unit or multiple unit. Using only the first!") : nothing
loco[1].n > 1 ? println("WARNING: the loaded file contains more than one traction unit or multiple unit. Using only one!") : nothing
Base.length(loco) == 0 ? error("No traction unit or multiple unit present in file '$file'") : nothing
loco = loco[1].data
cars = vehicles
## set the variables for locos
m_loco= loco["mass"] * 1000
haskey(loco, "a_braking") ? a_braking = loco["a_braking"] : nothing
haskey(loco, "base_resistance") ? f_Rtd0 = loco["base_resistance"] : nothing
haskey(loco, "rolling_resistance") ? f_Rtc0 = loco["rolling_resistance"] : nothing
haskey(loco, "air_resistance") ? F_Rt2 = loco["air_resistance"] * g * m_loco : nothing
haskey(loco, "mass_traction") ? m_td = loco["mass_traction"] * 1000 : m_td = m_t
haskey(loco, "rotation_mass") ? ξ_loco = loco["rotation_mass"] : nothing
m_tc = m_loco- m_td
haskey(loco, "tractive_effort") ? F_v_pairs = loco["tractive_effort"] : F_v_pairs = [ [0.0, m_td * g * μ],[v_limit*3.6, m_td * g * μ] ]
F_v_pairs = reduce(hcat,F_v_pairs)' # convert to matrix
F_v_pairs[:,1] ./= 3.6 # convert km/h to m/s
F_v_pairs = tuple.(eachcol(F_v_pairs)...) # convert each row to tuples
## set the variables for cars
if !isempty(cars)
resis_base = []
resis_roll = []
resis_air = []
rotMassFac = []
for car in cars
haskey(car.data, "base_resistance") ?
append!(resis_base,repeat([car.data["base_resistance"]],car.n)) :
append!(resis_base,repeat([f_Rw0],car.n))
haskey(car.data, "rolling_resistance") ?
append!(resis_roll,repeat([car.data["rolling_resistance"]],car.n)) :
append!(resis_roll,repeat([f_Rw1],car.n))
haskey(car.data, "air_resistance") ?
append!(resis_air,repeat([car.data["air_resistance"]],car.n)) :
append!(resis_air, repeat([f_Rw2],car.n))
haskey(car.data, "rotation_mass") ?
append!(rotMassFac,repeat([(car.data["rotation_mass"],car.data["mass"])],car.n)) :
append!(rotMassFac,repeat([(ξ_cars ,car.data["mass"])],car.n))
m_w += car.data["mass"] * car.n * 1000 # in kg
end
f_Rw0 = Statistics.mean(resis_base)
f_Rw1 = Statistics.mean(resis_roll)
f_Rw2 = Statistics.mean(resis_air)
carRotMass = 0
for elem in rotMassFac
carRotMass += elem[1]*elem[2] * 1000 # in kg
end
ξ_cars = carRotMass/m_w
ξ_train = (ξ_loco * m_loco+ carRotMass)/m_train_empty
else
ξ_cars = 0
ξ_train = ξ_loco
end
Train(
name, id, uuid, length,
m_train_full, m_td, m_tc, m_w,
ξ_train, ξ_loco, ξ_cars,
transportType, v_limit,
a_braking,
f_Rtd0, f_Rtc0, F_Rt2, f_Rw0, f_Rw1, f_Rw2,
F_v_pairs
)
end #function Train() # outer constructor
## create a moving section containing characteristic sections
function createMovingSection(path::Path, v_trainLimit::Real, s_trainLength::Real)

View File

@ -30,7 +30,6 @@
approxLevel = 6
v00 = 100/3.6 # velocity factor (in m/s)
g = 9.81 # acceleration due to gravity (in m/s^2) # TODO: should more digits of g be used? g=9,80665 m/s^2
## calculate forces
@ -43,7 +42,7 @@ Calculate the vehicle resistance for the traction unit of the `train` dependend
...
# Arguments
- `v::AbstractFloat`: the current velocity in m/s.
- `train::Dict`: ? ? ?
- `train::Train`: ? ? ?
...
# Examples
@ -52,36 +51,34 @@ julia> calcTractionUnitResistance(30.0, ? ? ?)
? ? ?
```
"""
function calcTractionUnitResistance(v::AbstractFloat, train::Dict)
function calcTractionUnitResistance(v::AbstractFloat, train::Train)
# equation is based on [Wende:2003, page 151]
f_Rtd0 = train[:f_Rtd0] # coefficient for basic resistance due to the traction units driving axles (in ‰)
f_Rtc0 = train[:f_Rtc0] # coefficient for basic resistance due to the traction units carring axles (in ‰)
F_Rt2 = train[:F_Rt2] # coefficient for air resistance of the traction units (in N)
m_td = train[:m_td] # mass on the traction unit's driving axles (in kg)
m_tc = train[:m_tc] # mass on the traction unit's carrying axles (in kg)
Δv_t = train[:Δv_t] # coefficient for velocitiy difference between traction unit and outdoor air (in m/s)
f_Rtd0 = train.f_Rtd0 # coefficient for basic resistance due to the traction units driving axles (in ‰)
f_Rtc0 = train.f_Rtc0 # coefficient for basic resistance due to the traction units carring axles (in ‰)
F_Rt2 = train.F_Rt2 # coefficient for air resistance of the traction units (in N)
m_td = train.m_td # mass on the traction unit's driving axles (in kg)
m_tc = train.m_tc # mass on the traction unit's carrying axles (in kg)
F_R_tractionUnit = f_Rtd0/1000 * m_td * g + f_Rtc0/1000 * m_tc * g + F_Rt2 * ((v + Δv_t) /v00)^2 # vehicle resistance of the traction unit (in N) # /1000 because of the unit ‰
# TODO: use calcForceFromCoefficient? F_R_tractionUnit = calcForceFromCoefficient(f_Rtd0, m_td) + calcForceFromCoefficient(f_Rtc0, m_tc) + F_Rt2 * ((v + Δv_t) /v00)^2 # vehicle resistance of the traction unit (in N)
F_R_tractionUnit = f_Rtd0/1000 * m_td * g + f_Rtc0/1000 * m_tc * g + F_Rt2 * ((v + Δv_air) /v00)^2 # vehicle resistance of the traction unit (in N) # /1000 because of the unit ‰
# TODO: use calcForceFromCoefficient? F_R_tractionUnit = calcForceFromCoefficient(f_Rtd0, m_td) + calcForceFromCoefficient(f_Rtc0, m_tc) + F_Rt2 * ((v + Δv_air) /v00)^2 # vehicle resistance of the traction unit (in N)
return F_R_tractionUnit
#TODO: same variable name like in the rest of the tool? return R_traction
#TODO: just one line? return train[:f_Rtd0]/1000*train[:m_td]*g+train[:f_Rtc0]/1000*train[:m_tc]*g+train[:F_Rt2]*((v+train[:Δv_t])/v00)^2 # /1000 because of the unit ‰
#TODO: just one line? return train.f_Rtd0/1000*train.m_td*g+train.f_Rtc0/1000*train.m_tc*g+train.F_Rt2*((v+train.Δv_air)/v00)^2 # /1000 because of the unit ‰
end #function calcTractionUnitResistance
"""
TODO
calculate and return the wagons vehicle resistance dependend on the velocity
"""
function calcWagonsResistance(v::AbstractFloat, train::Dict)
function calcWagonsResistance(v::AbstractFloat, train::Train)
# equation is based on a combination of the equations of Strahl and Sauthoff [Wende:2003, page 153] with more detailled factors (Lehmann, page 135)
f_Rw0 = train[:f_Rw0] # coefficient for basic resistance of the set of wagons (consist) (in ‰)
f_Rw1 = train[:f_Rw1] # coefficient for the consists resistance to rolling (in ‰)
f_Rw2 = train[:f_Rw2] # coefficient fo the consistsr air resistance (in ‰)
m_w = train[:m_w] # mass of the set of wagons (consist) (in kg)
Δv_w = train[:Δv_w] # coefficient for velocitiy difference between set of wagons (consist) and outdoor air (in m/s)
f_Rw0 = train.f_Rw0 # coefficient for basic resistance of the set of wagons (consist) (in ‰)
f_Rw1 = train.f_Rw1 # coefficient for the consists resistance to rolling (in ‰)
f_Rw2 = train.f_Rw2 # coefficient fo the consistsr air resistance (in ‰)
m_w = train.m_w # mass of the set of wagons (consist) (in kg)
F_R_wagons = m_w *g *(f_Rw0/1000 + f_Rw1/1000 *v /v00 + f_Rw2/1000 * ((v + Δv_w) /v00)^2) # vehicle resistance of the wagons (in N) # /1000 because of the unit ‰
# TODO: use calcForceFromCoefficient? F_R_wagons = calcForceFromCoefficient(f_Rw0, m_w) + calcForceFromCoefficient(f_Rw1, m_w) *v /v00 + calcForceFromCoefficient(f_Rw2, m_w) * ((v + Δv_w) /v00)^2 # vehicle resistance of the wagons (in N)
F_R_wagons = m_w *g *(f_Rw0/1000 + f_Rw1/1000 *v /v00 + f_Rw2/1000 * ((v + Δv_air) /v00)^2) # vehicle resistance of the wagons (in N) # /1000 because of the unit ‰
# TODO: use calcForceFromCoefficient? F_R_wagons = calcForceFromCoefficient(f_Rw0, m_w) + calcForceFromCoefficient(f_Rw1, m_w) *v /v00 + calcForceFromCoefficient(f_Rw2, m_w) * ((v + Δv_air) /v00)^2 # vehicle resistance of the wagons (in N)
return F_R_wagons
end #function calcWagonsResistance

View File

@ -1,35 +0,0 @@
#!/usr/bin/env julia
# -*- coding: UTF-8 -*-
# __julia-version__ = 1.7.2
# __author__ = "Max Kannenberg"
# __copyright__ = "2020-2022"
# __license__ = "ISC"
"""
Read the input information from YAML files for train, path and settings, save it in different dictionaries and return them.
"""
function importYamlFiles(trainDirectory::String, pathDirectory::String)
train = importFromYaml(:train, trainDirectory)
path = importFromYaml(:path, pathDirectory)
return (train, path)
end #function importYamlFiles
"""
Read the train information from a YAML file, save it in a Dict and return it.
"""
function importFromYaml(dataType::Symbol, directory::String)
dataSet = String(dataType)
data = YAML.load(open(directory))
if collect(keys(data))[1] != dataSet
error("ERROR at reading the ", dataSet, " yaml file: The data set is called ", collect(keys(data))[1]," and not ", dataSet, ".")
end
dataKeys = collect(keys(data[dataSet]))
dataKeys = collect(keys(data[dataSet]))
dataValues = collect(values(data[dataSet]))
dictionary = Dict()
for number in 1:length(dataKeys)
merge!(dictionary, Dict(Symbol(dataKeys[number]) => dataValues[number]))
end
return dictionary
end # function importFromYaml

View File

@ -5,7 +5,7 @@
# __copyright__ = "2020-2022"
# __license__ = "ISC"
function createOutput(train::Dict, settings::Settings, path::Path, movingSection::Dict, drivingCourse::Vector{Dict})
function createOutput(train::Train, settings::Settings, path::Path, movingSection::Dict, drivingCourse::Vector{Dict})
if settings.outputDetail == :running_time
output = movingSection[:t] # TODO: or use drivingCourse[end][:t]
@ -71,7 +71,7 @@ function createOutput(train::Dict, settings::Settings, path::Path, movingSection
end
#=
function createOutputDict(train::Dict, settings::Settings, path::Path, movingSection::Dict, drivingCourse::Vector{Dict})
function createOutputDict(train::Train, settings::Settings, path::Path, movingSection::Dict, drivingCourse::Vector{Dict})
outputDict = Dict{Symbol,Any}()
merge!(outputDict, Dict(:train => train, :path => path, :settings => settings))

View File

@ -1,7 +1,7 @@
#!/usr/bin/env julia
# -*- coding: UTF-8 -*-
# __julia-version__ = 1.7.2
# __author__ = "Max Kannenberg, Martin Scheidt"
# __author__ = "Martin Scheidt, Max Kannenberg"
# __copyright__ = "2022"
# __license__ = "ISC"
@ -28,686 +28,35 @@ struct Path
end #struct Path
"""
Read the train information from a YAML file, save it in a train Dict and return it.
"""
function checkAndSetTrain!(train::Dict)
# check train information from input dictionary
checkAndSetString!(train, "train", :name, "") # train's name
# add train's identifier if not existing
if !(haskey(train, :id) && train[:id]!=nothing)
merge!(train, Dict(:id =>1))
end
checkAndSetString!(train, "train", :type, "passenger", ["passenger", "freight"]) # train type "passenger" or "freight"
checkAndSetPositiveNumberWithDifferentNames!(train, "train", :length, :l_train, "m", 20.0) # total length (in m)
# TODO: or just use: checkAndSetPositiveNumber!(train, "train", :length, "m", 20.0)
checkAndSetSpeedLimit!(train) # train's speed limit (in m/s)
checkAndSetBrakingAcceleration!(train) # a_braking
checkAndSetPositiveNumber!(train, "train", :m_td, "kg", 80000) # mass on the traction unit's driving axles (in kg)
checkAndSetPositiveNumber!(train, "train", :m_tc, "kg", 0.0) # mass on the traction unit's carrying axles (in kg)
checkAndSetSum!(train, "train", :m_t, :m_td, :m_tc) # mass of the traction unit (in kg)
checkAndSetPositiveNumber!(train, "train", :m_w, "kg", 0.0) # mass of the set of wagons (consist) (in kg)
checkAndSetSum!(train, "train", :m_train, :m_t, :m_w) # total mass (in kg)
if train[:m_train] <= 0.0
error("ERROR at checking the input for the train: The train's mass has to be higher than 0.0 kg.")
end
checkAndSetRotationMassFactors!(train)
checkAndSetTractiveEffortVelocityPairs!(train) # pairs of velocity and tractive effort
# coefficients for the vehicle resistance of the traction unit
checkAndSetRealNumber!(train, "train", :Δv_t, "m/s", 15.0/3.6) # coefficient for velocitiy difference between traction unit and outdoor air (in m/s)
checkAndSetPositiveNumber!(train, "train", :f_Rtd0, "", 0.0) # coefficient for basic resistance due to the traction units driving axles (in ‰)
checkAndSetPositiveNumber!(train, "train", :f_Rtc0, "", 0.0) # coefficient for basic resistance due to the traction units carring axles (in ‰)
checkAndSetPositiveNumber!(train, "train", :F_Rt2, "N", 0.0) # coefficient for air resistance of the traction units (in N)
# coefficients for the vehicle resistance of the set of wagons (consist)
checkAndSetRealNumber!(train, "train", :Δv_w, "m/s", getDefault_Δv_w(train[:type])) # coefficient for velocitiy difference between set of wagons (consist) and outdoor air (in m/s)
checkAndSetPositiveNumber!(train, "train", :f_Rw0, "", 0.0) # coefficient for basic resistance of the set of wagons (consist) (in ‰)
checkAndSetPositiveNumber!(train, "train", :f_Rw1, "", 0.0) # coefficient for the consists resistance to rolling (in ‰)
checkAndSetPositiveNumber!(train, "train", :f_Rw2, "", 0.0) # coefficient fo the consistsr air resistance (in ‰)
# inform the user about keys of the input dictionary that are not used in this tool
usedKeys = [:name, :id, :type,
:length, :l_train, :v_limit, :v_limit_kmh, :a_braking,
:m_train, :m_t, :m_td, :m_tc, :m_w,
:ξ_train, :ξ_t, :ξ_w, :rotationMassFactor_train, :rotationMassFactor_t, :rotationMassFactor_w,
:tractiveEffortVelocityPairs, :F_T_pairs, :F_T_pairs_kmh,
:f_Rtd0, :f_Rtc0, :F_Rt2, :Δv_t,
:f_Rw0, :f_Rw1, :f_Rw2, :Δv_w]
informAboutUnusedKeys(collect(keys(train)), usedKeys::Vector{Symbol}, "train")
return train
end #function checkAndSetTrain!
function checkAndSetPath!(path::Path)
# check path information from input dictionary
checkAndSetString!(path, "path", :name, "")
# TODO checkId ? path[:id] # path identifier
checkAndSetSections!(path)
checkAndSetPOIs!(path)
# inform the user about keys of the input dictionary that are not used in this tool
usedKeys = [:name,
:sections, :sectionStarts, :sectionStarts_kmh,
:pointsOfInterest]
informAboutUnusedKeys(collect(keys(path)), usedKeys::Vector{Symbol}, "path")
return path
end # function checkAndSetPath!
function checkAndSetBool!(dictionary::Dict, dictionaryType::String, key::Symbol, defaultValue::Bool)
if haskey(dictionary,key) && dictionary[key]!=nothing
if typeof(dictionary[key]) != Bool
error("ERROR at checking the input for the ",dictionaryType,": The value of the key ",String(key)," is not correct. The value has to be of type Bool.")
end
else
merge!(dictionary, Dict(key => defaultValue))
defaultValue && println("INFO at checking the input for the ",dictionaryType,": The key ",String(key)," or its value is missing. Therefore ",String(key),"=",dictionary[key]," is assumed and used.")
end
return dictionary
end #function checkAndSetBool!
function checkAndSetPositiveNumber!(dictionary::Dict, dictionaryType::String, key::Symbol, unit::String, default::Real)
if haskey(dictionary,key) && dictionary[key]!=nothing
if typeof(dictionary[key]) <: Real && dictionary[key] >= 0.0
else
error("ERROR at checking the input for the ",dictionaryType,": The value of ",String(key)," is no real floating point number >=0.0.")
end
else
merge!(dictionary, Dict(key => default))
println("INFO at checking the input for the ",dictionaryType,": The key ",String(key)," is missing. Therefore ",String(key),"=",default," ",unit," will be assumed and used." )
end
return dictionary
end #function checkAndSetPositiveNumber!
# first method without a default value
function checkAndSetPositiveNumberWithDifferentNames!(dictionary::Dict, dictionaryType::String, mainKey::Symbol, alternativeKey::Symbol, unit::String)
mainKey_temp = -1.0
alternativeKey_temp = -1.0
if haskey(dictionary, mainKey) && dictionary[mainKey]!=nothing
if typeof(dictionary[mainKey]) <: Real && dictionary[mainKey] >= 0.0
mainKey_temp = dictionary[mainKey]
else
error("ERROR at checking the input for the ",dictionaryType,": The value of ",mainKey," is no real floating point number >=0.0.")
end
end
if haskey(dictionary, alternativeKey) && dictionary[alternativeKey]!=nothing
if typeof(dictionary[alternativeKey]) <: Real && dictionary[alternativeKey] >= 0.0
alternativeKey_temp = dictionary[alternativeKey]
else
error("ERROR at checking the input for the ",dictionaryType,": The value of ",alternativeKey," is no real floating point number >=0.0.")
end
else
delete!(dictionary, alternativeKey)
end
if mainKey_temp >= 0.0 && alternativeKey_temp >= 0.0
difference = abs(mainKey_temp - alternativeKey_temp)
if difference > 1/(10^approxLevel) # TODO or use difference > 0.0 ?
delete!(dictionary, alternativeKey)
println("WARNING at checking the input for the ",dictionaryType,": The values of ",mainKey," and ",alternativeKey," differ by ",difference," ",unit,". The value ",String(mainKey),"=",default," ",unit," is used." )
end
elseif mainKey_temp >= 0.0
# do nothing
elseif alternativeKey_temp >= 0.0
merge!(dictionary, Dict(mainKey => alternativeKey_temp))
else
# do nothing
end
return dictionary
end #function checkAndSetPositiveNumberWithDifferentNames!
# second method with a default value
function checkAndSetPositiveNumberWithDifferentNames!(dictionary::Dict, dictionaryType::String, mainKey::Symbol, alternativeKey::Symbol, unit::String, default::Real)
mainKey_temp = -1.0
alternativeKey_temp = -1.0
if haskey(dictionary, mainKey) && dictionary[mainKey]!=nothing
if typeof(dictionary[mainKey]) <: Real && dictionary[mainKey] >= 0.0
mainKey_temp = dictionary[mainKey]
else
error("ERROR at checking the input for the ",dictionaryType,": The value of ",mainKey," is no real floating point number >=0.0.")
end
end
if haskey(dictionary, alternativeKey) && dictionary[alternativeKey]!=nothing
if typeof(dictionary[alternativeKey]) <: Real && dictionary[alternativeKey] >= 0.0
alternativeKey_temp = dictionary[alternativeKey]
else
error("ERROR at checking the input for the ",dictionaryType,": The value of ",alternativeKey," is no real floating point number >=0.0.")
end
else
delete!(dictionary, alternativeKey)
end
if mainKey_temp >= 0.0 && alternativeKey_temp >= 0.0
difference = abs(mainKey_temp - alternativeKey_temp)
if difference > 1/(10^approxLevel) # TODO or use difference > 0.0 ?
delete!(dictionary, alternativeKey)
println("WARNING at checking the input for the ",dictionaryType,": The values of ",mainKey," and ",alternativeKey," differ by ",difference," ",unit,". The value ",String(mainKey),"=",default," ",unit," is used." )
end
elseif mainKey_temp >= 0.0
# do nothing
elseif alternativeKey_temp >= 0.0
merge!(dictionary, Dict(mainKey => alternativeKey_temp))
else
# set a default value
merge!(dictionary, Dict(mainKey, default))
println("INFO at checking the input for the ",dictionaryType,": The key ",mainKey," or its value is missing. Therefore the value ",String(mainKey),"=",default," ",unit," is used." )
end
return dictionary
end #function checkAndSetPositiveNumberWithDifferentNames!
function checkAndSetRealNumber!(dictionary::Dict, dictionaryType::String, key::Symbol, unit::String, default::Real)
if haskey(dictionary,key) && dictionary[key]!=nothing
if typeof(dictionary[key]) <: Real
else
error("ERROR at checking the input for the ",dictionaryType,": The value of ",String(key)," is no real number.")
end
else
merge!(dictionary, Dict(key => default))
println("INFO at checking the input for the ",dictionaryType,": The key ",String(key)," is missing. Therefore ",String(key),"=",default," ",unit," will be assumed and used." )
end
return dictionary
end #function checkAndSetRealNumber!
function checkAndSetSum!(dictionary::Dict, dictionaryType::String, sum::Symbol, summand1::Symbol, summand2::Symbol)
if haskey(dictionary,sum) && dictionary[sum]!=nothing
if typeof(dictionary[sum]) <: Real && dictionary[sum] >= 0.0
difference = abs(dictionary[sum] - (dictionary[summand1]+dictionary[summand2]))
if difference > 1/(10^approxLevel)
error("ERROR at checking the input for the ",dictionaryType,": The value of ",String(sum)," is not exactly the sum of ",String(summand1)," and ",String(summand2),". It differs by ",difference,".")
end
else
error("ERROR at checking the input for the ",dictionaryType,": The value of ",String(sum)," is no real floating point number >=0.0.")
end
else
merge!(dictionary, Dict(sum => dictionary[summand1]+dictionary[summand2]))
println("INFO at checking the input for the ",dictionaryType,": The key ",String(sum)," is missing. Therefore ",String(sum)," = ",String(summand1)," + ",String(summand2)," = ",dictionary[sum]," was calculated and will be used." )
end
return dictionary
end #function checkAndSetSum!
function checkAndSetString!(dictionary::Dict, dictionaryType::String, key::Symbol, defaultValue::String, validValues::Vector{String})
# TODO change checkAndAddString! to checkAndAddSymbol! ?
if haskey(dictionary,key) && dictionary[key]!=nothing
value = dictionary[key]
if typeof(value) == String
for validValue in validValues
if value == validValue
return dictionary
end
end
end
error("ERROR at checking the input for the ",dictionaryType,": The value of ",String(key)," is wrong. It has to be one of the following String values: ", validValues)
else
println("INFO at checking the input for the ",dictionaryType,": The key ",String(key)," is missing. It has to be one of the following String values: ", validValues,". For this calculation the default value '",defaultValue,"' will be used.")
merge!(dictionary, Dict(key => defaultValue))
end
return dictionary
end #function checkAndSetString!
# second method of function checkAndSetString! without validValues
function checkAndSetString!(dictionary::Dict, dictionaryType::String, key::Symbol, defaultValue::String)
if haskey(dictionary,key) && dictionary[key]!=nothing
value = dictionary[key]
if typeof(value) == String
return dictionary
end
error("ERROR at checking the input for the ",dictionaryType,": The value of ",String(key)," is wrong. It has to be of type String.")
else
println("INFO at checking the input for the ",dictionaryType,": The key ",String(key)," is missing. For this calculation the default value '",defaultValue,"' will be used.")
merge!(dictionary, Dict(key => defaultValue))
end
return dictionary
end #function checkAndSetString!
function checkAndSetSpeedLimit!(train::Dict)
v_limit_temp = 0.0
v_limit_kmh_temp = 0.0
if haskey(train, :v_limit) && train[:v_limit]!=nothing
if typeof(train[:v_limit]) <: Real && train[:v_limit] >= 0.0
v_limit_temp = train[:v_limit]
else
error("ERROR at checking the input for the train: The value of v_limit is no real floating point number >=0.0.")
end
end
if haskey(train, :v_limit_kmh) && train[:v_limit_kmh]!=nothing
if typeof(train[:v_limit_kmh]) <: Real && train[:v_limit_kmh] >= 0.0
v_limit_kmh_temp = train[:v_limit_kmh]
else
error("ERROR at checking the input for the train: The value of v_limit_kmh is no real floating point number >=0.0.")
end
else
delete!(train, :v_limit_kmh)
end
if v_limit_temp > 0.0 && v_limit_kmh_temp > 0.0
difference = abs(v_limit_temp - v_limit_kmh_temp/3.6)
if difference > 1/(10^approxLevel) # TODO or use difference > 0.0 ?
delete!(train, :v_limit_kmh)
println("WARNING at checking the input for the train: The values of v_limit and v_limit_kmh differ by ",difference," m/s. The value v_limit=",v_limit_temp," m/s is used." )
end
elseif v_limit_temp > 0.0
# do nothing
elseif v_limit_kmh_temp > 0.0
merge!(train, Dict(:v_limit => v_limit_kmh_temp/3.6))
else
# set a default value
merge!(train, Dict(:v_limit, 1000.0/3.6)) # set speed limit to 1000 km/h
println("INFO at checking the input for the train: There is no value for the trains speed limit (v_limit or v_limit_kmh). The value v_limit=1000 km/h =",train[:v_limit]," m/s is used." )
end
return train
end #function checkAndSetSpeedLimit!
function checkAndSetBrakingAcceleration!(train::Dict)
if haskey(train, :a_braking) && train[:a_braking]!=nothing
if typeof(train[:a_braking]) <: Real
if train[:a_braking] > 0.0
train[:a_braking] =-train[:a_braking]
println("INFO at checking the input for the train: The value for a_braking is >0.0. The braking acceleration has to be <0.0. Therefore a_braking=",train[:a_braking]," m/s^2 is used." )
elseif train[:a_braking] == 0.0
error("ERROR at checking the input for the train: The value for a_braking is 0.0. The braking acceleration has to be <0.0.")
end
else
error("ERROR at checking the input for the train: The value for a_braking is no real floating point number <0.0.")
end
else
# set a default value depending on the train type
if train[:type] == "freight"
a_braking = -0.225
elseif train[:type] == "passenger"
a_braking = -0.375
#elseif train[:type] == "passengerSuburban"
# a_braking = -0.525
# TODO: add suburban trains to train type?
end
merge!(train, Dict(:a_braking => a_braking))
println("INFO at checking the input for the train: The key for a_braking is missing. Because of the train type ",train[:type]," a_braking=",a_braking," m/s^2 will be assumed and used." )
end
return train
end #function checkAndSetBrakingAcceleration!
function checkAndSetRotationMassFactors!(train::Dict)
checkAndSetPositiveNumberWithDifferentNames!(train, "train", :ξ_train, :rotationMassFactor_train, "")
checkAndSetPositiveNumberWithDifferentNames!(train, "train", :ξ_t, :rotationMassFactor_t, "")
checkAndSetPositiveNumberWithDifferentNames!(train, "train", :ξ_w, :rotationMassFactor_w, "")
if haskey(train, :ξ_train) && train[:ξ_train]!=nothing
if train[:ξ_train]>0.0
if haskey(train, :ξ_t) && train[:ξ_t]!=nothing && train[:ξ_t]>0.0 && (train[:m_w]==0.0 || (haskey(train, :ξ_w) && train[:ξ_w]!=nothing))
# TODO: is && train[:ξ_t]>0.0 necessary here?
difference = abs(train[:ξ_train] - (train[:ξ_t]*train[:m_t] + train[:ξ_w]*train[:m_w])/train[:m_train])
if difference > 1/(10^approxLevel)
error("ERROR at checking the input for the train: The value of ξ_train is not exactly ξ_train=(ξ_t*m_t + ξ_w*m_w)/m_train. It differs by ",difference,".")
end
end
else
error("ERROR at checking the input for the train: The value of :ξ_train is no real floating point number >0.0.")
end
else
checkAndSetPositiveNumber!(train, "train", :ξ_t, "", 1.09)
if train[:m_w]>0.0
default_ξ_w = 1.06
else
default_ξ_w = 0.0
end
checkAndSetPositiveNumber!(train, "train", :ξ_w, "", default_ξ_w)
ξ_train=(train[:ξ_t]*train[:m_t] + train[:ξ_w]*train[:m_w])/train[:m_train] # rotation mass factor of the whole train (without unit)
if ξ_train <= 0.0
error("ERROR at checking the input for the train: The train's rotations mass factor has to be higher than 0.0 kg.")
end
merge!(train, Dict(:ξ_train => ξ_train))
end
return train
end #function checkAndSetRotationMassFactors!
function checkAndSetTractiveEffortVelocityPairs!(train::Dict) # pairs of velocity and tractive effort
if haskey(train,:tractiveEffortVelocityPairs) && train[:tractiveEffortVelocityPairs]!=nothing
pairs = train[:tractiveEffortVelocityPairs]
velocityMultiplier = 1.0
if (haskey(train,:F_T_pairs) && train[:F_T_pairs]!=nothing) && (haskey(train,:F_T_pairs_kmh) && train[:F_T_pairs_kmh]!=nothing)
println("WARNING at checking the input for the train: There are values for tractiveEffortVelocityPairs, F_T_pairs and F_T_pairs_kmh. The values for tractiveEffortVelocityPairs are used." )
elseif haskey(train,:F_T_pairs) && train[:F_T_pairs]!=nothing
println("WARNING at checking the input for the train: There are values for tractiveEffortVelocityPairs and F_T_pairs. The values for tractiveEffortVelocityPairs are used." )
elseif haskey(train,:F_T_pairs_kmh) && train[:F_T_pairs_kmh]!=nothing
println("WARNING at checking the input for the train: There are values for tractiveEffortVelocityPairs and F_T_pairs_kmh. The values for tractiveEffortVelocityPairs are used." )
end
elseif haskey(train,:F_T_pairs) && train[:F_T_pairs]!=nothing
pairs = train[:F_T_pairs]
velocityMultiplier = 1.0
if haskey(train,:F_T_pairs_kmh) && train[:F_T_pairs_kmh]!=nothing
println("WARNING at checking the input for the train: There are values for F_T_pairs and F_T_pairs_kmh. The values for F_T_pairs are used." )
end
elseif haskey(train,:F_T_pairs_kmh) && train[:F_T_pairs_kmh]!=nothing
velocityMultiplier = 1000/3600
pairs=[]
for row in 1:length(train[:F_T_pairs_kmh])
push!(pairs, [train[:F_T_pairs_kmh][row][1]*velocityMultiplier, train[:F_T_pairs_kmh][row][2]])
end # for
else
error("ERROR at checking the input for the train: There has to be the key tractiveEffortVelocityPairs filled with a list of pairs of velocity and tractive effort.")
end # if
# check if the elements of the array have the correct type
errorDetected=false
for row in 1:length(pairs)
if typeof(pairs[row][1]) <: Real && pairs[row][1]>=0.0
else
errorDetected=true
println("ERROR at checking the input for the train: The speed value of train[:tractiveEffortVelocityPairs] in row ", row ," is no real floating point number >=0.0.")
end
if typeof(pairs[row][2]) <: Real && pairs[row][2]>=0.0
else
errorDetected=true
println("ERROR at checking the input for the train: The tractive effort value of train[:tractiveEffortVelocityPairs] in row ", row ," is no real floating point number >=0.0.")
end
if row>=2 && pairs[row][1] <= pairs[row-1][1]
errorDetected=true
println("ERROR at checking the input for the train: The speed value of train[:tractiveEffortVelocityPairs] in row ", row ," (v=",pairs[row][1]," m/s) is not higher than the speed value in the previous row (v=",pairs[row-1][1]," m/s).")
end
end # for
if errorDetected
error("ERROR at checking the input for the train: Only real floating point number >=0.0 are allowed for speed and tractive effort. The speed values have to be listed from low to high.")
end
# create tractiveEffortVelocityPairs
if pairs[1][1]>0.0 # if there is no F_T for v=0.0, the first known value is used
newPairs=[]
push!(newPairs, [0.0, pairs[1][2]])
println("INFO at checking the input for the train: The tractive effort for v=0.0 m/s is missing. Therefore the first given value F_T(v=",pairs[1][1]," m/s)=",pairs[1][2]," N will be used." )
for row in 1:length(pairs)
push!(newPairs, [pairs[row][1], pairs[row][2]])
end # for
merge!(train, Dict(:tractiveEffortVelocityPairs => newPairs))
else
merge!(train, Dict(:tractiveEffortVelocityPairs => pairs))
end
if length(pairs[1])>2
println("INFO according the train dictionary: Only the first two columns of train[:tractiveEffortVelocityPairs] are used in this tool.")
end
return train
end #function checkAndSetTractiveEffortVelocityPairs!
function getDefault_Δv_w(type::String) # coefficient for velocitiy difference between set of wagons (consist) and outdoor air (in m/s)
if type == "passenger"
# TODO if different passenger or freight trains are posiible, use: if startswith(type, "passenger"). exanples: passengerLocomotivehauled and passengerMotorCoachTrain
Δv_w=15.0/3.6
elseif type == "freight"
Δv_w=0.0
end # if
return Δv_w
end #function getDefault_Δv_w!
# function checkAndSetSections!(path::Path)
# # check the section information
# if haskey(path,:sections) && path.sections!=nothing
# # TODO: check typeof(path.sections) == Dict
# if (haskey(path, :sectionStarts) && path[:sectionStarts]!=nothing) && (haskey(path,:sectionStarts_kmh) && path[:sectionStarts_kmh]!=nothing)
# println("WARNING at checking the input for the path: There are values for sections, sectionStarts and sectionStarts_kmh. The dictionary sections is used." )
# elseif haskey(path,:sectionStarts) && path[:sectionStarts]!=nothing
# println("WARNING at checking the input for the path: There are values for sections and sectionStarts. The dictionary sections is used." )
# elseif haskey(path,:sectionStarts_kmh) && path[:sectionStarts_kmh]!=nothing
# println("WARNING at checking the input for the path: There are values for sections and sectionStarts_kmh. The dictionary sections is used." )
# end
# elseif haskey(path,:sectionStarts) && path[:sectionStarts]!=nothing
# # TODO: check typeof(path.sections) == Array
# createSections!(path, :sectionStarts)
# if haskey(path,:sectionStarts_kmh) && path[:sectionStarts_kmh]!=nothing
# println("WARNING at checking the input for the path: There are values for sectionStarts and sectionStarts_kmh. The array sectionStarts is used." )
# end
# elseif haskey(path,:sectionStarts_kmh) && path[:sectionStarts_kmh]!=nothing
# # TODO: check typeof(path.sections) == Array
# createSections!(path, :sectionStarts_kmh)
# else
# error("ERROR at checking the input for the path: The Symbol :sections is missing. It has to be added with a list of sections. Each has to be a dictionary with the keys :s_tart, :s_end, :v_limit and :f_Rp.")
# section = Dict(:s_start => 0.0,
# :s_end => 15.0,
# :v_limit => 1000.0/3.6,
# :f_Rp => 0.0)
# merge!(path, Dict(:sections => [section]))
# return path
# end
# sections = path.sections
# checkedSections = []
# increasing = false
# decreasing = false
# #TODO: throw error for each issue or collect the issues and use the Bool errorDetected like in createSections?
# # check values for section==1
# checkAndSetRealNumber!(sections[1], "path.sections[1]", :s_start, "m", 0.0) # first point of the section (in m)
# checkAndSetRealNumber!(sections[1], "path.sections[1]", :s_end, "m", 0.0) # first point of the next section (in m)
# checkAndSetPositiveNumber!(sections[1], "path.sections[1]", :v_limit, "m/s", 1000.0/3.6) # paths speed limt (in m/s)
# checkAndSetRealNumber!(sections[1], "path.sections[1]", :f_Rp, "‰", 0.0) # specific path resistance of the section (in ‰)
# push!(checkedSections, sections[1])
# if sections[1][:s_start] < sections[1][:s_end]
# increasing = true
# elseif sections[1][:s_start] > sections[1][:s_end]
# decreasing = true
# else
# pop!(checkedSections)
# println("WARNING at checking the input for the path: The first section of :sections has the same position for starting and end point. The section will be deleted and not used in the tool.")
# end
# for sectionNr in 2:length(sections)
# checkAndSetRealNumber!(sections[sectionNr], "path.sections["*string(sectionNr)*"]", :s_start, "m", sections[sectionNr-1][:s_end]) # first point of the section (in m)
# # TODO how to define default values? which has to be checked and defined fist? s_end-1 and s_start need each other as default values
# #if sectionNr < length(sections) && haskey(sections[sectionNr], :s_start) && sections[sectionNr][:s_start]!=nothing && typeof(sections[sectionNr][:s_start]) <: Real
# # defaultEnd = sections[sectionNr+1][:s_start]
# #end
# defaultEnd = sections[sectionNr][:s_start] # so the default value for s_end creates a sections of lenght=0.0 #TODO should be changed!
# checkAndSetRealNumber!(sections[sectionNr], "path.sections["*string(sectionNr)*"]", :s_end, "m", defaultEnd) # first point of the next section (in m)
# checkAndSetPositiveNumber!(sections[sectionNr], "path.sections["*string(sectionNr)*"]", :v_limit, "m/s", 1000.0/3.6) # paths speed limt (in m/s)
# checkAndSetRealNumber!(sections[sectionNr], "path.sections["*string(sectionNr)*"]", :f_Rp, "‰", 0.0) # specific path resistance of the section (in ‰)
# push!(checkedSections, sections[sectionNr])
# # compare the section's start and end position
# if sections[sectionNr][:s_start] < sections[sectionNr][:s_end]
# increasing = true
# elseif sections[sectionNr][:s_start] > sections[sectionNr][:s_end]
# decreasing = true
# else
# pop!(checkedSections)
# println("INFO at checking the input for the path: The ",sectionNr,". section of :sections has the same position for starting and end point. The section will be deleted and not used in the tool.")
# end
# if increasing && decreasing
# error("ERROR at checking the input for the path: The positions of the :sections are not increasing/decreasing consistently. The direction in the ",sectionNr,". section differs from the previous.")
# end
# if length(checkedSections)>1 && sections[sectionNr][:s_start] != checkedSections[end-1][:s_end]
# error("ERROR at checking the input for the path.sections: The starting position of the ",section,". section (s=",sections[sectionNr][:s_start]," m) does not euqal the last position of the previous section(s=",checkedSections[end-1][:s_end]," m). The sections have to be sequential.")
# # TODO: maybe if there is a gab create a new section and only if there a jumps in the wrong direction throw an error?
# end
# end #for
# return path
# end #function checkAndSetSections!
# function createSections!(path::Path, key::Symbol)
# # read the section starting positions and corresponding information
# if key == :sectionStarts
# sectionStartsArray = path[:sectionStarts]
# conversionFactor = 1.0 # conversion factor between the units m/s and m/s
# if haskey(path,:sectionStarts) && path[:sectionStarts_kmh]!=nothing
# println("WARNING at checking the input for the path: There are values for sectionStarts and sectionStarts_kmh. The values for sectionStarts are used." )
# end
# elseif key == :sectionStarts_kmh
# sectionStartsArray = path[:sectionStarts_kmh]
# conversionFactor = 1/3.6 # conversion factor between the units km/h and m/s
# elseif key == :characteristic_sections
# sectionStartsArray = path[:characteristic_sections]
# conversionFactor = 1/3.6 # conversion factor between the units km/h and m/s
# else
# error("ERROR at checking the input for the path: The keyword sectionStarts or sectionStarts_kmh is missing. The sections can not be created without them.")
# end # if
# # check if the array is correct and if elements of the array have the correct type and valid values
# errorDetected = false
# if length(sectionStartsArray)<2
# error("ERROR at checking the input for the path: The keyword ",key," needs at least two rows for two points each with the three columns [s, v_limit, f_Rp].")
# end
# for row in 1:length(sectionStartsArray)
# if length(sectionStartsArray[row])>=3
# if length(sectionStartsArray[row])>3
# println("INFO at checking the input for the path: Only the first three columns of sectionStartsArray are used in this tool.")
# end
# else
# error("ERROR at checking the input for the path: The keyword ",key," needs to be filled with the three columns [s, v_limit, f_Rp].")
# end
# if !(typeof(sectionStartsArray[row][1]) <: Real)
# errorDetected=true
# println("ERROR at checking the input for the path: The position value (column 1) of ",key," in row ", row ," is no real floating point number.")
# end
# if !(typeof(sectionStartsArray[row][2]) <: Real && sectionStartsArray[row][2] >= 0.0)
# errorDetected=true
# println("ERROR at checking the input for the path: The speed limit (column 2) of ",key," in row ", row ," is no real floating point number >=0.0.")
# end
# if !(typeof(sectionStartsArray[row][3]) <: Real)
# errorDetected=true
# println("ERROR at checking the input for the path: The tractive effort value (column 3) of ",key," in row ", row ," is no real floating point number.")
# end
# end # for
# if errorDetected
# error("ERROR at checking the input for the path: The values of ",key," have to be corrected.")
# end
# sections = []
# for row in 2:length(sectionStartsArray)
# s_start = sectionStartsArray[row-1][1] # first point of the section (in m)
# s_end = sectionStartsArray[row][1] # first point of the next section (in m)
# v_limit = sectionStartsArray[row-1][2]*conversionFactor # paths speed limt (in m/s)
# f_Rp = sectionStartsArray[row-1][3] # specific path resistance of the section (in ‰)
# section = Dict(:s_start => s_start,
# :s_end => s_end,
# :v_limit => v_limit,
# :f_Rp => f_Rp)
# push!(sections, section)
# end # for
# # s_start in first entry defines the path's beginning
# # s_end in last entry defines the path's ending
# merge!(path, Dict(:sections => sections))
# return path
# end #function createSections!
# function checkAndSetPOIs!(path::Path)
# # read the section starting positions and corresponding information
# if haskey(path, :pointsOfInterest)
# # if path.poi != nothing
# pointsOfInterest = path[:points_of_interest]
# sortingNeeded = false
# errorDetected = false
# for element in 1:length(pointsOfInterest)
# if typeof(pointsOfInterest[element]) <: Real
# if element > 1
# if pointsOfInterest[element] < pointsOfInterest[element-1]
# sortingNeeded = true
# println("INFO at checking the input for the path: The point of interest in element ", element ," (",pointsOfInterest[element]," m) has to be higher than the value of the previous element (",pointsOfInterest[element-1]," m). The points of interest will be sorted.")
# end
# end
# else
# errorDetected = true
# println("ERROR at checking the input for the path: The point of interest in element ", element ," is no real floating point number.")
# end
# end # for
# if errorDetected
# error("ERROR at checking the input for the path: The values of pointsOfInterest have to be corrected.")
# end
# if sortingNeeded == true
# sort!(pointsOfInterest)
# end
# copiedPOIs = []
# for element in 1:length(pointsOfInterest)
# if element == 1
# push!(copiedPOIs, pointsOfInterest[element])
# elseif element > 1 && pointsOfInterest[element] > pointsOfInterest[element-1]
# push!(copiedPOIs, pointsOfInterest[element])
# end
# end # for
# path[:points_of_interest ] = copiedPOIs
# # else
# # println("INFO at checking the input for the path: The key pointsOfInterest exists but without values.")
# # delete!(path, :points_of_interest)
# # end
# end
# return path
# end #function checkAndSetPOIs!
#function informAboutUnusedKeys(dictionary::Dict, dictionaryType::String) # inform the user which Symbols of the input dictionary are not used in this tool
function informAboutUnusedKeys(allKeys::AbstractVector, usedKeys::Vector{Symbol}, dictionaryType::String) # inform the user which Symbols of the input dictionary are not used in this tool
unusedKeys = []
# find unused keys in allKeys
for key in allKeys
used = false
for usedKey in usedKeys
if key == usedKey
used = true
break
end
end
if !used
push!(unusedKeys, key)
end
end
if length(unusedKeys)>0
println("INFO at checking the input for the ",dictionaryType,": The following Keywords are not used in this tool:")
for key in unusedKeys
println(" - ",key)
end
end
end #function informAboutUnusedKeys
struct Train
name::String # a name or description of the train
id::String # a short string as identifier
uuid::UUID # a unique identifier
length::Real # train length in meter
m_train_full::Real # mass of the full loaded train in kilogram
m_td::Real # mass on driving axles of the traction unit in kilogram
m_tc::Real # mass on the traction unit's carrying axles in kilogram
m_w::Real # mass of the set of wagons/cars/consist in kilogram
ξ_train::Real # rotation mass factor
ξ_loco::Real # rotation mass factor
ξ_cars::Real # rotation mass factor
transportType::Symbol # ":freight" or ":passenger" for resistance calculation
v_limit::Real # in m/s
a_braking::Real # in m/s^2
# coefficients for the vehicle resistance
# for the traction unit (F_Rt=f_Rtd0*m_td*g+f_Rtc0*m_tc*g+F_Rt2*((v+Δv_air)/v00)^2)
f_Rtd0::Real # coefficient for basic resistance due to the traction units driving axles (in ‰)
f_Rtc0::Real # coefficient for basic resistance due to the traction units carring axles (in ‰)
F_Rt2::Real # coefficient for air resistance of the traction units (in N)
# for the consist (set of wagons) (F_Rw=m_w*g*(f_Rw0+f_Rw1*v/v00+f_Rw2*((v+Δv_air)/v00)^2))
f_Rw0::Real # coefficient for the consists basic resistance (in ‰)
f_Rw1::Real # coefficient for the consists resistance to rolling (in ‰)
f_Rw2::Real # coefficient fo the consistsr air resistance (in ‰)
# tractive effort as pairs of speed and tractive effort
tractiveEffort::Vector{Tuple{Real, Real}} # [v in m/s, F_T in N]
end #struct Train

View File

@ -14,9 +14,9 @@ settings = Dict()
@testset "load data" begin
println("testing load train data")
push!(trains, :freight => @time TrainRuns.importFromYaml(:train, "test/data/trains/freight.yaml"))
push!(trains, :local => @time TrainRuns.importFromYaml(:train, "test/data/trains/local.yaml"))
push!(trains, :longdistance => @time TrainRuns.importFromYaml(:train, "test/data/trains/longdistance.yaml"))
push!(trains, :freight => @time Train("test/data/trains/freight.yaml"))
push!(trains, :local => @time Train("test/data/trains/local.yaml"))
push!(trains, :longdistance => @time Train("test/data/trains/longdistance.yaml"))
println("testing load path data")
push!(paths, :const => @time Path("test/data/paths/const.yaml"))
@ -79,7 +79,7 @@ anticipated = Dict(
@time result = trainrun(test[1][2],test[2][2])
expected = anticipated[:default][Symbol(test_name)]
# compare result to test data set
@test isapprox(result, expected, atol=0.01)
@test isapprox(result, expected, rtol=0.1)
println("--------------------")
end