diff --git a/CHANGELOG.md b/CHANGELOG.md index 992cad2..f0281f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/Project.toml b/Project.toml index d09852c..1fbfcb9 100644 --- a/Project.toml +++ b/Project.toml @@ -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" diff --git a/src/TrainRuns.jl b/src/TrainRuns.jl index e511c52..54997db 100644 --- a/src/TrainRuns.jl +++ b/src/TrainRuns.jl @@ -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.") diff --git a/src/behavior.jl b/src/behavior.jl index ef03b07..7ddae8c 100644 --- a/src/behavior.jl +++ b/src/behavior.jl @@ -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]=0.0 -# println("Warning: a_braking gets to high in CS ",CS[:id], " with a=",previousPoint[:a] ," > ",train[:a_braking]) +# if 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) diff --git a/src/calc.jl b/src/calc.jl index 879f3d2..e9ed5ef 100644 --- a/src/calc.jl +++ b/src/calc.jl @@ -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? diff --git a/src/characteristics.jl b/src/characteristics.jl index f0e3baf..7abcd10 100644 --- a/src/characteristics.jl +++ b/src/characteristics.jl @@ -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 diff --git a/src/constructors.jl b/src/constructors.jl index f795ed4..d6d156f 100644 --- a/src/constructors.jl +++ b/src/constructors.jl @@ -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) diff --git a/src/formulary.jl b/src/formulary.jl index 1be57a6..4bf6125 100644 --- a/src/formulary.jl +++ b/src/formulary.jl @@ -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 diff --git a/src/import.jl b/src/import.jl deleted file mode 100644 index aae95af..0000000 --- a/src/import.jl +++ /dev/null @@ -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 diff --git a/src/output.jl b/src/output.jl index ae5d25b..c7165be 100644 --- a/src/output.jl +++ b/src/output.jl @@ -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)) diff --git a/src/types.jl b/src/types.jl index 0123f15..4a663c9 100644 --- a/src/types.jl +++ b/src/types.jl @@ -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 diff --git a/test/runtests.jl b/test/runtests.jl index 3476764..655f41e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -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