diff --git a/CHANGELOG.md b/CHANGELOG.md index a9b7176..37241e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ Categories: Added, Changed, Deprecated, Removed, Fixed, and Security. * modified test to work with Julia Testsets and with simplier naming of input files * renamed Validate.jl into types.jl * renamed TrainRunCalc.jl into calc.jl -* changed capital letter of include files to lower letter +* changed tilte of include files from upper case to lower case * changed seperation of submodules into a single module with file include ### Removed diff --git a/src/behavior.jl b/src/behavior.jl new file mode 100644 index 0000000..d1815d2 --- /dev/null +++ b/src/behavior.jl @@ -0,0 +1,1398 @@ +#!/usr/bin/env julia +# -*- coding: UTF-8 -*- +# __julia-version__ = 1.7.2 +# __author__ = "Max Kannenberg" +# __copyright__ = "2020-2022" +# __license__ = "ISC" + +## functions for calculating tractive effort and resisting forces +""" + calculateTractiveEffort(v, tractiveEffortVelocityPairs) + +Calculate the trains tractive effort with the `tractiveEffortVelocityPairs` dependend on the velocity `v`. + +... +# 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. +... + +# Examples +```julia-repl +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]]) +80000 +``` +""" +function calculateTractiveEffort(v::AbstractFloat, tractiveEffortVelocityPairs) + for row in 1:length(tractiveEffortVelocityPairs) + nextPair = tractiveEffortVelocityPairs[row] + if nextPair[1] == v + return nextPair[2] + elseif nextPair[1] > v + # interpolate for a straight line between the two surrounding points with the formula: F=(v-v_(row-1))*(F_row-F_(row-1))/(v_row-v_(row-1))+F_(row-1) + previousPair = tractiveEffortVelocityPairs[row-1] + F_T_interpolation = (v-previousPair[1]) * (nextPair[2]-previousPair[2]) / (nextPair[1]-previousPair[1]) + previousPair[2] + return F_T_interpolation + end #if + end #for + # if v gets higher than the velocities in tractiveEffortVelocityPairs the last tractive effort will be used + # TODO: also an extrapolation could be used + return tractiveEffortVelocityPairs[end][2] +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) + + if massModel == :mass_point + pathResistance = calcForceFromCoefficient(CSs[csId][:r_path], train[:m_train]) + elseif massModel == :homogeneous_strip + pathResistance = 0.0 + 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]) + 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]) + end #if + end #while + end #if + + return pathResistance +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) + # calculate resisting forces + dataPoint[:R_traction] = calcTractionUnitResistance(dataPoint[:v], train) + dataPoint[:R_wagons] = calcWagonsResistance(dataPoint[:v], train) + dataPoint[:R_train] = dataPoint[:R_traction] + dataPoint[:R_wagons] + dataPoint[:R_path] = calculatePathResistance(CSs, csId, dataPoint[:s], massModel, train) + dataPoint[:F_R] = dataPoint[:R_train] + dataPoint[:R_path] + + # calculate tractive effort + 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])) + else # bsType == "accelerating" || bsType == "diminishing" || 'default' + dataPoint[:F_T] = calculateTractiveEffort(dataPoint[:v], train[:tractiveEffortVelocityPairs]) + end + + return dataPoint +end #function calculateForces! + + +""" +TODO +""" +function moveAStep(previousPoint::Dict, stepVariable::Symbol, stepSize::Real, csId::Integer) + # stepSize is the currentStepSize depending on the accessing function + # TODO: csId is only for error messages. Should it be removed? + #= 08/31 TODO: How to check if the train stopps during this step? I should throw an error myself that I catch in higher hierarchies. =# + + # create the next data point + newPoint = createDataPoint() + newPoint[:i] = previousPoint[:i]+1 # identifier + + # calculate s, t, v, E + if stepVariable == :distance # distance step method + newPoint[:Δs] = stepSize # step size (in m) + if previousPoint[:a] == 0.0 + if previousPoint[:v] == 0.0 + error("ERROR: The train tries to cruise at v=0.0 m/s at s=",previousPoint[:s]," in CS",csId,".") + end + newPoint[:Δt] = calc_Δt_with_constant_v(newPoint[:Δs], previousPoint[:v]) # step size (in s) + newPoint[:Δv] = 0.0 # step size (in m/s) + else + # check if the parts of the following square roots will be <0.0 in the functions calc_Δt_with_Δs and calc_Δv_with_Δs + squareRootPartIsNegative = (previousPoint[:v]/previousPoint[:a])^2+2*newPoint[:Δs]/previousPoint[:a] < 0.0 || previousPoint[:v]^2+2*newPoint[:Δs]*previousPoint[:a] < 0.0 + if previousPoint[:a] < 0.0 && squareRootPartIsNegative + error("ERROR: The train stops during the accelerating section in CS",csId," because the tractive effort is lower than the resistant forces.", + " Before the stop the last point has the values s=",previousPoint[:s]," m, v=",previousPoint[:v]," m/s, a=",previousPoint[:a]," m/s^2,", + " F_T=",previousPoint[:F_T]," N, R_traction=",previousPoint[:R_traction]," N, R_wagons=",previousPoint[:R_wagons]," N, R_path=",previousPoint[:R_path]," N.") + end + newPoint[:Δt] = calc_Δt_with_Δs(newPoint[:Δs], previousPoint[:a], previousPoint[:v]) # step size (in s) + newPoint[:Δv] = calc_Δv_with_Δs(newPoint[:Δs], previousPoint[:a], previousPoint[:v]) # step size (in m/s) + end + + elseif stepVariable == :time # time step method + newPoint[:Δt] = stepSize # step size (in s) + newPoint[:Δs] = calc_Δs_with_Δt(newPoint[:Δt], previousPoint[:a], previousPoint[:v]) # step size (in m) + newPoint[:Δv] = calc_Δv_with_Δt(newPoint[:Δt], previousPoint[:a]) # step size (in m/s) + + elseif stepVariable == :velocity # velocity step method + if previousPoint[:a] == 0.0 + if previousPoint[:v] == 0.0 + error("ERROR: The train tries to cruise at v=0.0 m/s at s=",previousPoint[:s]," in CS",csId,".") + end + newPoint[:Δs] = stepSize # step size (in m) + # TODO what is the best default step size for constant v? define Δs or Δt? + newPoint[:Δt] = calc_Δt_with_constant_v(newPoint[:Δs], previousPoint[:v]) # step size (in s) + newPoint[:Δv] = 0.0 # step size (in m/s) + else + newPoint[:Δv] = stepSize * sign(previousPoint[:a]) # step size (in m/s) + newPoint[:Δs] = calc_Δs_with_Δv(newPoint[:Δv], previousPoint[:a], previousPoint[:v]) # step size (in m) + newPoint[:Δt] = calc_Δt_with_Δv(newPoint[:Δv], previousPoint[:a]) # step size (in s) + end + end #if + + newPoint[:s] = previousPoint[:s] + newPoint[:Δs] # position (in m) + newPoint[:t] = previousPoint[:t] + newPoint[:Δt] # point in time (in s) + newPoint[:v] = previousPoint[:v] + newPoint[:Δv] # velocity (in m/s) + newPoint[:ΔW] = calc_ΔW(previousPoint[:F_T], newPoint[:Δs]) # mechanical work in this step (in Ws) + newPoint[:W] = previousPoint[:W] + newPoint[:ΔW] # mechanical work (in Ws) + newPoint[:ΔE] = calc_ΔE(newPoint[:ΔW]) # energy consumption in this step (in Ws) + newPoint[:E] = previousPoint[:E] + newPoint[:ΔE] # energy consumption (in Ws) + + + return newPoint +end #function moveAStep + +""" +# 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 +""" +function getCurrentSpeedLimit(CSs::Vector{Dict}, csWithTrainHeadId::Integer, s::Real, trainLength::Real) + v_limit = CSs[csWithTrainHeadId][:v_limit] + s_exit = CSs[csWithTrainHeadId][:s_exit] + if csWithTrainHeadId > 1 && s -trainLength < CSs[csWithTrainHeadId][:s_entry] + formerCsId = csWithTrainHeadId-1 + while formerCsId > 0 && s -trainLength < CSs[formerCsId][:s_exit] + if CSs[formerCsId][:v_limit] < v_limit # TODO: is the position of the train's rear < movingSection[:s_entry], v_limit of the first CS is used + v_limit = CSs[formerCsId][:v_limit] + s_exit = CSs[formerCsId][:s_exit] + end + formerCsId = formerCsId -1 + end + end + currentSpeedLimit = Dict(:v => v_limit, :s_end => s_exit + trainLength) + return currentSpeedLimit +end #function getCurrentSpeedLimit + +function getNextPointOfInterest(pointsOfInterest::Vector{Real}, s::Real) + for s_POI in pointsOfInterest + if s_POI > s + return s_POI + end + end + error("ERROR in getNextPointOfInterest: There is no POI higher than s=",s," m.") +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}) + # conditions for the break free section + endOfCSReached = drivingCourse[end][:s] >= CS[:s_exit] || stateFlags[:endOfCSReached] + trainIsHalting = drivingCourse[end][:v] == 0.0 + + if trainIsHalting && !endOfCSReached + BS = createBehaviorSection("breakFree", drivingCourse[end][:s], drivingCourse[end][:v], drivingCourse[end][:i]) + drivingCourse[end][:behavior] = BS[:type] + + # traction effort and resisting forces (in N) + calculateForces!(drivingCourse[end], CSs, CS[:id], "accelerating", train, settings.massModel) # currently the tractive effort is calculated like in the accelerating section + + # calculate the breakFree section with calculating the accelerating section and just using the first step and removing the rest + try (CS, drivingCourse, stateFlags) = addAcceleratingSection!(CS, drivingCourse, stateFlags, settings, train, CSs) + catch(acceleratingError) + println("This error happened during the break free phase that is using the accelerating function:") + rethrow(acceleratingError) + end + + # delete every dataPoint except the first two + while drivingCourse[end][:i] > drivingCourse[BS[:dataPoints][1]][:i] +1 + pop!(drivingCourse) + end + + # change the accelerating data to break free + drivingCourse[end-1][:behavior] = BS[:type] + drivingCourse[end][:behavior] = BS[:type] + push!(BS[:dataPoints], drivingCourse[end][:i]) + + # remove the accelerating section from the CS + CS[:t] = CS[:t] - get(CS[:behaviorSections], :accelerating, Dict(:t=>0.0))[:t] # total running time (in s) + CS[:E] = CS[:E] - get(CS[:behaviorSections], :accelerating, Dict(:E=>0.0))[:E] # total energy consumption (in Ws) + delete!(CS[:behaviorSections], :accelerating) + + # calculate the accumulated breakFree section information + merge!(BS, Dict(:length => drivingCourse[end][:s] - BS[:s_entry], # total length (in m) + :s_exit => drivingCourse[end][:s], # last position (in m) + :t => drivingCourse[end][:t] - drivingCourse[BS[:dataPoints][1]][:t], # total running time (in s) + :E => drivingCourse[end][:E] - drivingCourse[BS[:dataPoints][1]][:E], # total energy consumption (in Ws) + :v_exit => drivingCourse[end][:v])) # exit speed (in m/s))) + + CS[:t] = CS[:t] + BS[:t] # total running time (in s) + CS[:E] = CS[:E] + BS[:E] # total energy consumption (in Ws) + + merge!(CS[:behaviorSections], Dict(:breakFree => BS)) + end # else: return the characteristic section without a breakFree section + + # determine state flags + + if haskey(stateFlags, :usedForDefiningCharacteristics) && stateFlags[:usedForDefiningCharacteristics] + s_braking = 0.0 + else + s_braking = calcBrakingDistance(drivingCourse[end][:v], CS[:v_exit], train[:a_braking]) + end + + # reset state flags + stateFlags[:endOfCSReached] = drivingCourse[end][:s] >= CS[:s_exit] + stateFlags[:brakingStartReached] = drivingCourse[end][:s] +s_braking >= CS[:s_exit] + stateFlags[:tractionDeficit] = drivingCourse[end][:F_T] < drivingCourse[end][:F_R] # or add another flag for equal forces? + stateFlags[:resistingForceNegative] = drivingCourse[end][:F_R] < 0 + stateFlags[:previousSpeedLimitReached] = false + stateFlags[:speedLimitReached] = drivingCourse[end][:v] >= CS[:v_limit] + stateFlags[:error] = !(stateFlags[:endOfCSReached] || stateFlags[:brakingStartReached] || stateFlags[:tractionDeficit] || stateFlags[:previousSpeedLimitReached] || stateFlags[:speedLimitReached]) + + return (CS, drivingCourse, stateFlags) +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}) + if stateFlags[:previousSpeedLimitReached] + 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]) + end + + s_clearing = min(CS[:s_exit]-drivingCourse[end][:s]-s_braking, currentSpeedLimit[:s_end] - drivingCourse[end][:s]) + if s_clearing > 0.0 + (CS, drivingCourse, stateFlags) = addCruisingSection!(CS, drivingCourse, stateFlags, s_clearing, settings, train, CSs, "clearing") + calculateForces!(drivingCourse[end], CSs, CS[:id], "accelerating", train, settings.massModel) + # stateFlags[:brakingStartReached] = brakingStartReached + # stateFlags[:endOfCSReached] = stateFlags[:endOfCSReached] || drivingCourse[end][:s] == CS[:s_exit] + else + 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]) + stateFlags[:previousSpeedLimitReached] = currentSpeedLimit[:v] != CS[:v_limit] && drivingCourse[end][:v] >= currentSpeedLimit[:v] + else + stateFlags[:error] = true + end + + return (CS, drivingCourse, stateFlags) +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) + CSs = movingSection[:characteristicSections] + CS = CSs[csId] + drivingCourse = movingSection[:drivingCourse]=# + + calculateForces!(drivingCourse[end], CSs, CS[:id], "accelerating", train, settings.massModel) + + 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]) + end + + # conditions for the accelerating section + targetSpeedReached = drivingCourse[end][:v] >= CS[:v_peak] || stateFlags[:speedLimitReached] + endOfCSReached = drivingCourse[end][:s] >= CS[:s_exit] || stateFlags[:endOfCSReached] + tractionSurplus = drivingCourse[end][:F_T] > drivingCourse[end][:F_R] + brakingStartReached = drivingCourse[end][:s] +s_braking >= CS[:s_exit] || stateFlags[:brakingStartReached] + previousSpeedLimitReached = stateFlags[:previousSpeedLimitReached] + + # use the conditions for the accelerating section + if !targetSpeedReached && !endOfCSReached && tractionSurplus && !brakingStartReached + 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]) + previousSpeedLimitReached = currentSpeedLimit[:v] != CS[:v_limit] && drivingCourse[end][:v] >= currentSpeedLimit[:v] + speedLimitReached = drivingCourse[end][:v] >= CS[:v_limit] + #speedLimitReached = drivingCourse[end][:v] > currentSpeedLimit[:v] + #targetSpeedReached = speedLimitReached + while !targetSpeedReached && !endOfCSReached && tractionSurplus && !brakingStartReached && !previousSpeedLimitReached + currentStepSize = settings.stepSize # initialize the step size that can be reduced near intersections + nextPointOfInterest = getNextPointOfInterest(CS[:pointsOfInterest], drivingCourse[end][:s]) + pointOfInterestReached = drivingCourse[end][:s] >= nextPointOfInterest + + 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]) + 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 && 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]) + end + + # acceleration (in m/s^2): + drivingCourse[end][:a] = calcAcceleration(drivingCourse[end][:F_T], drivingCourse[end][:F_R], train[:m_train], train[:ξ_train]) + + # create the next data point + push!(drivingCourse, moveAStep(drivingCourse[end], settings.stepVariable, currentStepSize, CS[:id])) + drivingCourse[end][:behavior] = BS[:type] + push!(BS[:dataPoints], drivingCourse[end][:i]) + + calculateForces!(drivingCourse[end], CSs, CS[:id], BS[:type], train, settings.massModel) + + # conditions for the next while cycle + if !ignoreBraking + 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] + previousSpeedLimitReached = currentSpeedLimit[:v] < CS[:v_limit] && (drivingCourse[end][:v] > currentSpeedLimit[:v] || (drivingCourse[end][:v] == currentSpeedLimit[:v] && drivingCourse[end][:s] < currentSpeedLimit[:s_end])) + targetSpeedReached = drivingCourse[end][:v] >= CS[:v_peak] + #targetSpeedReached = speedLimitReached + pointOfInterestReached = drivingCourse[end][:s] >= nextPointOfInterest # POIs include s_exit as well + tractionSurplus = drivingCourse[end][:F_T] > drivingCourse[end][:F_R] + end #while + + if CS[:id]==0 + testFlag = true + else + testFlag = false # for testing + end + + # check which limit was reached and adjust the currentStepSize for the next cycle + if cycle < settings.approxLevel+1 + if drivingCourse[end][:F_T] <= drivingCourse[end][:F_R] + testFlag && println("in CS",CS[:id]," accelerating cycle",cycle," case: F_T=", drivingCourse[end][:F_T]," <= F_R=",drivingCourse[end][:F_R]) # for testing + currentStepSize = settings.stepSize / 10.0^cycle + + elseif s_braking > 0.0 && drivingCourse[end][:s] + s_braking > CS[:s_exit] + testFlag && println("in CS",CS[:id]," accelerating cycle",cycle," case: s +s_braking=", drivingCourse[end][:s],",+",s_braking," = ",drivingCourse[end][:s] +s_braking," > s_exit=",CS[:s_exit]) # for testing + currentStepSize = settings.stepSize / 10.0^cycle + + elseif drivingCourse[end][:s] > nextPointOfInterest + testFlag && println("in CS",CS[:id]," accelerating cycle",cycle," case: s=", drivingCourse[end][:s]," > nextPOI=",nextPointOfInterest) # for testing + if settings.stepVariable == :distance + currentStepSize = nextPointOfInterest - drivingCourse[end-1][:s] + else + currentStepSize = settings.stepSize / 10.0^cycle + end + + elseif drivingCourse[end][:v] > CS[:v_peak] + testFlag && println("in CS",CS[:id]," accelerating cycle",cycle," case: v=", drivingCourse[end][:v]," > v_peak=",CS[:v_peak]) # for testing + if settings.stepVariable == :speed + currentStepSize = CS[:v_peak]-drivingCourse[end-1][:v] + else + currentStepSize = settings.stepSize / 10.0^cycle + end + + elseif drivingCourse[end][:v] > currentSpeedLimit[:v] + testFlag && println("in CS",CS[:id]," accelerating cycle",cycle," case: v=", drivingCourse[end][:v]," > v_limitCurrent=",currentSpeedLimit[:v]) # for testing + if settings.stepVariable == :velocity + currentStepSize = currentSpeedLimit[:v]-drivingCourse[end-1][:v] + + else + currentStepSize = settings.stepSize / 10.0^cycle + end + + elseif drivingCourse[end][:s] + s_braking == CS[:s_exit] + testFlag && println("in CS",CS[:id]," accelerating cycle",cycle," case: s +s_braking=", drivingCourse[end][:s],",+",s_braking," = ",drivingCourse[end][:s] +s_braking," == s_exit=",CS[:s_exit]) # for testing + if s_braking == 0.0 + endOfCSReached = true + end + break + + elseif drivingCourse[end][:v] == CS[:v_peak] + testFlag && println("in CS",CS[:id]," accelerating cycle",cycle," case: v=", drivingCourse[end][:v]," == v_peak=",CS[:v_peak]) # for testing + break + + elseif drivingCourse[end][:v] == currentSpeedLimit[:v] && drivingCourse[end][:s] < currentSpeedLimit[:s_end] + testFlag && println("in CS",CS[:id]," accelerating cycle",cycle," case: v=", drivingCourse[end][:v]," == v_limitCurrent=",currentSpeedLimit[:v]) # for testing + break + + elseif drivingCourse[end][:s] == nextPointOfInterest + testFlag && println("in CS",CS[:id]," accelerating cycle",cycle," case: s=", drivingCourse[end][:s]," == nextPOI=",nextPointOfInterest) # for testing + if nextPointOfInterest == CS[:s_exit] + endOfCSReached = true + end + break + + else + println("v=",drivingCourse[end][:v]," v_peak= ", CS[:v_peak] , " v_cLimit=", currentSpeedLimit[:v]) + println("s=" ,drivingCourse[end][:s]," s_exit=", CS[:s_exit], " s+s_braking=", drivingCourse[end][:s] +s_braking," nextPOI=",nextPointOfInterest) + println("F_T=",drivingCourse[end][:F_T] ," F_R=", drivingCourse[end][:F_R]) + + error("ERROR at accelerating section: With the step variable ",settings.stepVariable," the while loop will be left although v CS[:v_peak] + testFlag && println("in CS",CS[:id]," accelerating cycle",cycle," case: v=", drivingCourse[end][:v]," > v_peak=",CS[:v_peak]) # for testing + pop!(drivingCourse) + pop!(BS[:dataPoints]) + + # conditions for the next section + brakingStartReached = false + + elseif drivingCourse[end][:s] + s_braking > CS[:s_exit] + testFlag && println("in CS",CS[:id]," accelerating cycle",cycle," case: s +s_braking=", drivingCourse[end][:s],",+",s_braking," = ",drivingCourse[end][:s] +s_braking," > s_exit=",CS[:s_exit]) # for testing + if s_braking > 0.0 + pop!(drivingCourse) + pop!(BS[:dataPoints]) + + else + drivingCourse[end][:s] = CS[:s_exit] # round s down to CS[:s_exit] + drivingCourse[end][:Δs] = drivingCourse[end][:s] - drivingCourse[end-1][:s] + end + + elseif drivingCourse[end][:s] > nextPointOfInterest + testFlag && println("in CS",CS[:id]," accelerating cycle",cycle," case: s=", drivingCourse[end][:s]," > nextPointOfInterest=",nextPointOfInterest) # for testing + drivingCourse[end][:s] = nextPointOfInterest # round s down to nextPointOfInterest + drivingCourse[end][:Δs] = drivingCourse[end][:s] - drivingCourse[end-1][:s] + + elseif drivingCourse[end][:F_T] <= drivingCourse[end][:F_R] + testFlag && println("in CS",CS[:id]," accelerating cycle",cycle," case: F_T=", drivingCourse[end][:F_T]," <= F_R=",drivingCourse[end][:F_R]) # for testing + + elseif drivingCourse[end][:v] > currentSpeedLimit[:v] + testFlag && println("in CS",CS[:id]," accelerating cycle",cycle," case: v=", drivingCourse[end][:v]," > v_limitCurrent=",currentSpeedLimit[:v]) # for testing + previousSpeedLimitReached = true + + pop!(drivingCourse) + pop!(BS[:dataPoints]) + + else + if drivingCourse[end][:s] + s_braking == CS[:s_exit] + testFlag && println("in CS",CS[:id]," accelerating cycle",cycle," else case and there: s +s_braking=", drivingCourse[end][:s],",+",s_braking," = ",drivingCourse[end][:s] +s_braking," > s_exit=",CS[:s_exit]) # for testing + elseif drivingCourse[end][:v] == currentSpeedLimit[:v] + testFlag && println("in CS",CS[:id]," accelerating cycle",cycle," case: v=", drivingCourse[end][:v]," == v_limitCurrent=",currentSpeedLimit[:v]) # for testing + + end + end + + # TODO is it possible to put this into to the if-fork? + if drivingCourse[end][:s] == CS[:s_exit] + endOfCSReached = true + end + + end + end #for + + if drivingCourse[end][:s] == CS[:s_exit] + endOfCSReached = true + end + + end #while + + if length(BS[:dataPoints]) > 1 + # calculate the accumulated accelerating section information + merge!(BS, Dict(:length => drivingCourse[end][:s] - BS[:s_entry], # total length (in m) + :s_exit => drivingCourse[end][:s], # last position (in m) + :t => drivingCourse[end][:t] - drivingCourse[BS[:dataPoints][1]][:t], # total running time (in s) + :E => drivingCourse[end][:E] - drivingCourse[BS[:dataPoints][1]][:E], # total energy consumption (in Ws) + :v_exit => drivingCourse[end][:v])) # exit speed (in m/s))) + + # 03/10 old: CS[:v_peak] = max(drivingCourse[end][:v], CS[:v_entry]) # setting v_peak to the last data points velocity which is the highest reachable value in this characteristic section or to v_entry in case it is higher when running on a path with high resistances + CS[:t] = CS[:t] + BS[:t] # total running time (in s) + CS[:E] = CS[:E] + BS[:E] # total energy consumption (in Ws) + + mergeBehaviorSection!(CS[:behaviorSections], BS) + end + end + + # set state flags + stateFlags[:endOfCSReached] = endOfCSReached + stateFlags[:brakingStartReached] = brakingStartReached + stateFlags[:tractionDeficit] = !(tractionSurplus || drivingCourse[end][:F_T] == drivingCourse[end][:F_R]) # or add another flag for equal forces? + stateFlags[:resistingForceNegative] = drivingCourse[end][:F_R] < 0 + stateFlags[:previousSpeedLimitReached] = previousSpeedLimitReached + stateFlags[:speedLimitReached] = targetSpeedReached + stateFlags[:error] = !(endOfCSReached || brakingStartReached || stateFlags[:tractionDeficit] || previousSpeedLimitReached || targetSpeedReached) + + return (CS, drivingCourse, stateFlags) +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) + trainIsClearing = cruisingType == "clearing" + trainIsBrakingDownhill = cruisingType == "downhillBraking" + + # traction effort and resisting forces (in N) + if !trainIsBrakingDownhill # TODO: or just give BS[:type] instead of "cruising"/"braking"? + calculateForces!(drivingCourse[end], CSs, CS[:id], "cruising", train, settings.massModel) + else + calculateForces!(drivingCourse[end], CSs, CS[:id], "braking", train, settings.massModel) + end + + 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]) + end + + # conditions for cruising section + #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] + targetPositionReached = s_cruising == 0.0 + resistingForceNegative = drivingCourse[end][:F_R] < 0 +#println(" vor if speedIsValid=",speedIsValid ," brakingStartReached=", brakingStartReached," tractionDeficit=", tractionDeficit," targetPositionReached=", targetPositionReached) + + if speedIsValid && !brakingStartReached && !tractionDeficit && !targetPositionReached + # 03/04 old: if drivingCourse[end][:v]>0.0 && drivingCourse[end][:v]<=CS[:v_peak] && !brakingStartReached && drivingCourse[end][:F_T] >= drivingCourse[end][:F_R] + BS = createBehaviorSection(cruisingType, drivingCourse[end][:s], drivingCourse[end][:v], drivingCourse[end][:i]) + drivingCourse[end][:behavior] = BS[:type] + # TODO: necessary? + s_cruising = min(s_cruising, CS[:s_exit]-BS[:s_entry]) + + # traction effort and resisting forces (in N) +#03/25 calculateForces!(drivingCourse[end], CSs, CS[:id], "cruising", train, settings.massModel) + if !trainIsBrakingDownhill + calculateForces!(drivingCourse[end], CSs, CS[:id], "cruising", train, settings.massModel) + else + calculateForces!(drivingCourse[end], CSs, CS[:id], "braking", train, settings.massModel) + end + + if settings.massModel == :homogeneous_strip && CS[:id] > 1 + # conditions for cruising section + 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] + # TODO: change? to correctCruisingType = (trainIsClearing || (trainIsBrakingDownhill == drivingCourse[end][:F_R] < 0)) # while clearing tractive or braking force can be used +#&& targetSpeedReached + # use the conditions for the cruising section + while trainInPreviousCS && !targetPositionReached && !tractionDeficit && (trainIsClearing || (trainIsBrakingDownhill == resistingForceNegative)) # while clearing tractive or braking force can be used + currentStepSize = settings.stepSize + nextPointOfInterest = getNextPointOfInterest(CS[:pointsOfInterest], drivingCourse[end][:s]) + pointOfInterestReached = drivingCourse[end][:s] >= nextPointOfInterest + + 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 && 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): +#03/25 drivingCourse[end][:F_T] = min(drivingCourse[end][:F_T], max(0.0, drivingCourse[end][:F_R])) + if !trainIsBrakingDownhill + drivingCourse[end][:F_T] = min(drivingCourse[end][:F_T], max(0.0, drivingCourse[end][:F_R])) + else + drivingCourse[end][:F_T] = 0.0 + end + + + # acceleration (in m/s^2): + drivingCourse[end][:a] = 0.0 + + # create the next data point + 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? + end + drivingCourse[end][:behavior] = BS[:type] + push!(BS[:dataPoints], drivingCourse[end][:i]) + + # traction effort and resisting forces (in N) + calculateForces!(drivingCourse[end], CSs, CS[:id], "default", train, settings.massModel) +# calculateForces!(drivingCourse[end], CSs, CS[:id], "cruising", train, settings.massModel) + #if !trainIsBrakingDownhill + # calculateForces!(drivingCourse[end], CSs, CS[:id], "cruising", train, settings.massModel) + #else + # calculateForces!(drivingCourse[end], CSs, CS[:id], "braking", train, settings.massModel) + #end + + # conditions for the next while cycle + pointOfInterestReached = drivingCourse[end][:s] >= nextPointOfInterest # 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] + resistingForceNegative = drivingCourse[end][:F_R] < 0.0 + end #while + + # check which limit was reached and adjust the currentStepSize for the next cycle + if cycle < settings.approxLevel+1 + if drivingCourse[end][:F_T] < drivingCourse[end][:F_R] + currentStepSize = settings.stepSize / 10.0^cycle + + elseif !trainIsBrakingDownhill && resistingForceNegative + currentStepSize = settings.stepSize / 10.0^cycle + + elseif trainIsBrakingDownhill && !resistingForceNegative + currentStepSize = settings.stepSize / 10.0^cycle + + elseif drivingCourse[end][:s] > nextPointOfInterest + if settings.stepVariable == :distance + currentStepSize = nextPointOfInterest - drivingCourse[end-1][:s] + else + 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])) + if settings.stepVariable == :distance + currentStepSize=BS[:s_entry] + s_cruising-drivingCourse[end-1][:s] + else + currentStepSize = settings.stepSize / 10.0^cycle + end + + 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] + break + + elseif drivingCourse[end][:s] == nextPointOfInterest + break + + elseif !trainInPreviousCS + break + + else + error("ERROR at cruising section: With the step variable ",settings.stepVariable," the while loop will be left although the if cases don't apply in CS",CS[:id]," with s=" ,drivingCourse[end][:s]," m and v=",drivingCourse[end][:v]," m/s") + end + + # delete last data point for recalculating the last step with reduced step size + pop!(drivingCourse) + pop!(BS[:dataPoints]) + + # conditions for the next for cycle + pointOfInterestReached = false + tractionDeficit = false + targetPositionReached = false + trainInPreviousCS = true + resistingForceNegative = drivingCourse[end][:F_R] < 0.0 + + else # if the level of approximation is reached + if drivingCourse[end][:s] > nextPointOfInterest + drivingCourse[end][:s] = nextPointOfInterest # round s down to nextPointOfInterest + drivingCourse[end][:Δs] = drivingCourse[end][:s] - drivingCourse[end-1][:s] + elseif drivingCourse[end][:s] > BS[:s_entry]+s_cruising + if BS[:type] != "clearing" + pop!(drivingCourse) + pop!(BS[:dataPoints]) + end + elseif drivingCourse[end][:s] == BS[:s_entry]+s_cruising + break + elseif drivingCourse[end][:F_T] < drivingCourse[end][:F_R] + break + elseif !trainIsBrakingDownhill && resistingForceNegative + break + elseif trainIsBrakingDownhill && !resistingForceNegative + break + elseif !trainInPreviousCS + break + + else + + end + end + end #for + end #while + end #if + + # conditions for the next while cycle + targetPositionReached = drivingCourse[end][:s] >= BS[:s_entry] +s_cruising + tractionDeficit = drivingCourse[end][:F_T] < drivingCourse[end][:F_R] + resistingForceNegative = drivingCourse[end][:F_R] < 0.0 + + while !targetPositionReached && !tractionDeficit && (trainIsClearing || (trainIsBrakingDownhill == resistingForceNegative)) # while clearing tractive or braking force can be used + # 03/09 old: while drivingCourse[end][:s] < BS[:s_entry]+s_cruising && drivingCourse[end][:F_T] >= drivingCourse[end][:F_R] + nextPointOfInterest = min(BS[:s_entry]+s_cruising, getNextPointOfInterest(CS[:pointsOfInterest], drivingCourse[end][:s])) + + # tractive effort (in N): +#03/25 drivingCourse[end][:F_T] = min(drivingCourse[end][:F_T], max(0.0, drivingCourse[end][:F_R])) + if !trainIsBrakingDownhill + drivingCourse[end][:F_T] = min(drivingCourse[end][:F_T], max(0.0, drivingCourse[end][:F_R])) + else + drivingCourse[end][:F_T] = 0.0 + end + + + + drivingCourse[end][:a] = 0.0 # acceleration (in m/s^2) + + # calculate the remaining cruising way + #s_cruisingRemaining=BS[:s_entry] + s_cruising-drivingCourse[end][:s] + s_cruisingRemaining = min(nextPointOfInterest -drivingCourse[end][:s], BS[:s_entry] +s_cruising -drivingCourse[end][:s]) + + # create the next data point + push!(drivingCourse, moveAStep(drivingCourse[end], :distance, s_cruisingRemaining, CS[:id])) + drivingCourse[end][:behavior] = BS[:type] + push!(BS[:dataPoints], drivingCourse[end][:i]) + + calculateForces!(drivingCourse[end], CSs, CS[:id], "default", train, settings.massModel) +# calculateForces!(drivingCourse[end], CSs, CS[:id], "cruising", train, settings.massModel) + #if !trainIsBrakingDownhill + # calculateForces!(drivingCourse[end], CSs, CS[:id], "cruising", train, settings.massModel) + #else + # calculateForces!(drivingCourse[end], CSs, CS[:id], "braking", train, settings.massModel) + #end + + # conditions for the next while cycle + targetPositionReached = drivingCourse[end][:s] >= BS[:s_entry] +s_cruising + tractionDeficit = drivingCourse[end][:F_T] < drivingCourse[end][:F_R] + resistingForceNegative = drivingCourse[end][:F_R] < 0 + end #while + + # TODO: realize this better inside the upper loops? + + + # calculate the accumulated cruising section information + merge!(BS, Dict(:length => drivingCourse[end][:s] - BS[:s_entry], # total length (in m) + :s_exit => drivingCourse[end][:s], # last position (in m) + :t => drivingCourse[end][:t] - drivingCourse[BS[:dataPoints][1]][:t], # total running time (in s) + :E => drivingCourse[end][:E] - drivingCourse[BS[:dataPoints][1]][:E], # total energy consumption (in Ws) + :v_exit => drivingCourse[end][:v])) # exit speed (in m/s))) + + CS[:t] = CS[:t] + BS[:t] # total running time (in s) + CS[:E] = CS[:E] + BS[:E] # total energy consumption (in Ws) + + mergeBehaviorSection!(CS[:behaviorSections], BS) + end # else: return the characteristic section without a cruising section + + # 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]) + 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]) + stateFlags[:previousSpeedLimitReached] = currentSpeedLimit[:v] != CS[:v_limit] && drivingCourse[end][:v] >= currentSpeedLimit[:v] + stateFlags[:error] = !(targetPositionReached || tractionDeficit || !(cruisingType == "clearing" || ((cruisingType == "downhillBraking") == resistingForceNegative))) + + return (CS, drivingCourse, stateFlags) +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}) + calculateForces!(drivingCourse[end], CSs, CS[:id], "diminishing", train, settings.massModel) + + 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]) + 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]) + brakingStartReached = drivingCourse[end][:s] + s_braking >= CS[:s_exit] || stateFlags[:brakingStartReached] + + # use the conditions for the diminishing section + if tractionDeficit && !targetSpeedReached && !endOfCSReached + BS = createBehaviorSection("diminishing", drivingCourse[end][:s], drivingCourse[end][:v], drivingCourse[end][:i]) + drivingCourse[end][:behavior] = BS[:type] + + while tractionDeficit && !targetSpeedReached && !endOfCSReached && !brakingStartReached + currentStepSize=settings.stepSize # initialize the step size that can be reduced near intersections + nextPointOfInterest = getNextPointOfInterest(CS[:pointsOfInterest], drivingCourse[end][:s]) + pointOfInterestReached = drivingCourse[end][:s] >= nextPointOfInterest + + 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 tractionDeficit && !brakingStartReached && !pointOfInterestReached && !targetSpeedReached + # 03/09 old: while drivingCourse[end][:F_T] < drivingCourse[end][:F_R] && !brakingStartReached && drivingCourse[end][:s] < nextPointOfInterest && 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]) + + # create the next data point + push!(drivingCourse, moveAStep(drivingCourse[end], settings.stepVariable, currentStepSize, CS[:id])) + drivingCourse[end][:behavior] = BS[:type] + push!(BS[:dataPoints], drivingCourse[end][:i]) + + calculateForces!(drivingCourse[end], CSs, CS[:id], BS[:type], train, settings.massModel) + + # conditions for the next while cycle + if !ignoreBraking + 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 + targetSpeedReached = drivingCourse[end][:v] <= 0.0 + tractionDeficit = drivingCourse[end][:F_T] < drivingCourse[end][:F_R] + endOfCSReached = drivingCourse[end][:s] == CS[:s_exit] + end #while + + if CS[:id]==0 + testFlag = true + else + testFlag = false # for testing + end + + # check which limit was reached and adjust the currentStepSize for the next cycle + if cycle < settings.approxLevel+1 + if drivingCourse[end][:v] < 0.0 + if settings.stepVariable == velocity + currentStepSize = drivingCourse[end-1][:v] + else + currentStepSize = settings.stepSize / 10.0^cycle + end + elseif drivingCourse[end][:F_T] > drivingCourse[end][:F_R] + testFlag && println("in CS",CS[:id]," diminishing cycle",cycle," case: F_T=", drivingCourse[end][:F_T]," > F_R=",drivingCourse[end][:F_R]) # for testing + currentStepSize = settings.stepSize / 10.0^cycle + + elseif s_braking > 0.0 && drivingCourse[end][:s] + s_braking > CS[:s_exit] + testFlag && println("in CS",CS[:id]," diminishing cycle",cycle," case: s +s_braking=", drivingCourse[end][:s],"+",s_braking," = ",drivingCourse[end][:s] +s_braking," > s_exit=",CS[:s_exit]) # for testing + currentStepSize = settings.stepSize / 10.0^cycle + + elseif drivingCourse[end][:s] > nextPointOfInterest + testFlag && println("in CS",CS[:id]," diminishing cycle",cycle," case: s=", drivingCourse[end][:s]," > nextPOI=",nextPointOfInterest) # for testing + if settings.stepVariable == :distance + currentStepSize = nextPointOfInterest - drivingCourse[end-1][:s] + else + currentStepSize = settings.stepSize / 10.0^cycle + end + + elseif drivingCourse[end][:s] + s_braking == CS[:s_exit] + testFlag && println("in CS",CS[:id]," diminishing cycle",cycle," case: s +s_braking=", drivingCourse[end][:s],"+",s_braking," = ",drivingCourse[end][:s] +s_braking," == s_exit=",CS[:s_exit]) # for testing + break + + elseif drivingCourse[end][:s] == nextPointOfInterest + testFlag && println("in CS",CS[:id]," diminishing cycle",cycle," case: s=", drivingCourse[end][:s]," == nextPOI=",nextPointOfInterest) # for testing + break + + elseif drivingCourse[end][:F_T] == drivingCourse[end][:F_R] + testFlag && println("in CS",CS[:id]," diminishing cycle",cycle," case: F_T=", drivingCourse[end][:F_T]," == F_R=",drivingCourse[end][:F_R]) # for testing + break + + elseif drivingCourse[end][:v] == 0.0 + error("ERROR: The train stops during diminishing run in CS",CS[:id]," at position s=",drivingCourse[end][:s]," m because the maximum tractive effort is lower than the resistant forces.", + " Before the stop the last point has the values s=",drivingCourse[end-1][:s]," m v=",drivingCourse[end-1][:v]," m/s a=",drivingCourse[end-1][:a]," m/s^2", + " F_T=",drivingCourse[end-1][:F_T]," N R_traction=",drivingCourse[end-1][:R_traction]," N R_wagons=",drivingCourse[end-1][:R_wagons]," N R_path=",drivingCourse[end-1][:R_path]," N.") + + else + error("ERROR during diminishing run: With the step variable ",settings.stepVariable," the while loop will be left although s+s_braking0.0 in CS",CS[:id]," with s=" ,drivingCourse[end][:s]," m and v=",drivingCourse[end][:v]," m/s") + end + # delete last data point for recalculating the last step with reduced step size + pop!(drivingCourse) + pop!(BS[:dataPoints]) + + # conditions for the next for cycle + brakingStartReached = false + pointOfInterestReached = false + targetSpeedReached = false + tractionDeficit = true + endOfCSReached = false + + else # if the level of approximation is reached + if drivingCourse[end][:v] <= 0.0 + testFlag && println("in CS",CS[:id]," diminishing cycle",cycle," case: v=", drivingCourse[end][:v]," <= 0.0") # for testing + # push!(BS[:dataPoints], drivingCourse[end][:i]) + error("ERROR: The train stops during diminishing run in CS",CS[:id]," because the maximum tractive effort is lower than the resistant forces.", + " Before the stop the last point has the values s=",drivingCourse[end-1][:s]," m v=",drivingCourse[end-1][:v]," m/s a=",drivingCourse[end-1][:a]," m/s^2", + " F_T=",drivingCourse[end-1][:F_T]," N R_traction=",drivingCourse[end-1][:R_traction]," N R_wagons=",drivingCourse[end-1][:R_wagons]," N R_path=",drivingCourse[end-1][:R_path]," N.") + + elseif s_braking > 0.0 && drivingCourse[end][:s] + s_braking > CS[:s_exit] + testFlag && println("in CS",CS[:id]," diminishing cycle",cycle," case: s +s_braking=", drivingCourse[end][:s],"+",s_braking," = ",drivingCourse[end][:s] +s_braking," > s_exit=",CS[:s_exit]) # for testing + pop!(drivingCourse) + pop!(BS[:dataPoints]) + + pointOfInterestReached = false + targetSpeedReached = false + tractionDeficit = true + endOfCSReached = false + + elseif drivingCourse[end][:s] > nextPointOfInterest + testFlag && println("in CS",CS[:id]," diminishing cycle",cycle," case: s=", drivingCourse[end][:s]," > nextPointOfInterest=",nextPointOfInterest) # for testing + drivingCourse[end][:s] = nextPointOfInterest # round s down to nextPointOfInterest + drivingCourse[end][:Δs] = drivingCourse[end][:s] - drivingCourse[end-1][:s] + + elseif drivingCourse[end][:F_T] >= drivingCourse[end][:F_R] + testFlag && println("in CS",CS[:id]," diminishing cycle",cycle," case: F_T=", drivingCourse[end][:F_T]," >= F_R=", drivingCourse[end][:F_R]) # for testing + break + + else + testFlag && println("in CS",CS[:id]," diminishing cycle",cycle," case: else with v=", drivingCourse[end][:v]," > 0.0 and F_T=", drivingCourse[end][:F_T]," <= F_R=", drivingCourse[end][:F_R]) # for testing + #println(" and s +s_braking=", drivingCourse[end][:s],"+",s_braking," = ",drivingCourse[end][:s] +s_braking," <= s_exit=",CS[:s_exit]) # for testing + #println(" and s=", drivingCourse[end][:s]," <= nextPointOfInterest=",nextPointOfInterest) # for testing + + # if drivingCourse[end][:s] + s_braking == CS[:s_exit] + # brakingStartReached = true + # end + end #if + + # # TODO is it possible to put this into to the if-fork? + # if drivingCourse[end][:s] == CS[:s_exit] + # endOfCSReached = true + # end + end #if + end #for + end #while + + if length(BS[:dataPoints]) > 1 # TODO: necessary? May it be possible that there is no diminishing because braking has to start? + # calculate the accumulated diminishing section information + merge!(BS, Dict(:length => drivingCourse[end][:s] - BS[:s_entry], # total length (in m) + :s_exit => drivingCourse[end][:s], # last position (in m) + :t => drivingCourse[end][:t] - drivingCourse[BS[:dataPoints][1]][:t], # total running time (in s) + :E => drivingCourse[end][:E] - drivingCourse[BS[:dataPoints][1]][:E], # total energy consumption (in Ws) + :v_exit => drivingCourse[end][:v])) # exit speed (in m/s))) + + CS[:t] = CS[:t] + BS[:t] # total running time (in s) + CS[:E] = CS[:E] + BS[:E] # total energy consumption (in Ws) + + mergeBehaviorSection!(CS[:behaviorSections], BS) + end + end + + # set state flags + stateFlags[:endOfCSReached] = endOfCSReached + stateFlags[:brakingStartReached] = brakingStartReached + stateFlags[:tractionDeficit] = tractionDeficit + stateFlags[:resistingForceNegative] = drivingCourse[end][:F_R] < 0 + stateFlags[:speedLimitReached] = drivingCourse[end][:v] >= CS[:v_peak] + stateFlags[:error] = !(endOfCSReached || brakingStartReached || !tractionDeficit) + + return (CS, drivingCourse, stateFlags) +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}) + # 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 + + # conditions for coasting section + 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]) + brakingStartReached = drivingCourse[end][:s] + s_braking >= CS[:s_exit] || stateFlags[:brakingStartReached] + + # use the conditions for the coasting section + if !targetSpeedReached && !endOfCSReached + BS = createBehaviorSection("coasting", drivingCourse[end][:s], drivingCourse[end][:v], drivingCourse[end][:i]) + drivingCourse[end][:behavior] = BS[:type] + + while !targetSpeedReached && !endOfCSReached && !brakingStartReached + currentStepSize=settings.stepSize # initialize the step size that can be reduced near intersections + nextPointOfInterest = getNextPointOfInterest(CS[:pointsOfInterest], drivingCourse[end][:s]) + pointOfInterestReached = drivingCourse[end][:s] >= nextPointOfInterest + + 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 !targetSpeedReached && !brakingStartReached && !pointOfInterestReached + # 03/09 old : while drivingCourse[end][:v] > CS[:v_exit] && drivingCourse[end][:v] <= CS[:v_peak] && !brakingStartReached && drivingCourse[end][:s] < nextPointOfInterest + # traction effort and resisting forces (in N): + 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]) + + # create the next data point + push!(drivingCourse, moveAStep(drivingCourse[end], settings.stepVariable, currentStepSize, CS[:id])) + drivingCourse[end][:behavior] = BS[:type] + push!(BS[:dataPoints], drivingCourse[end][:i]) + + # conditions for the next while cycle + 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 + targetSpeedReached = drivingCourse[end][:v] <= CS[:v_exit] || drivingCourse[end][:v] > CS[:v_peak] + end # while + + testFlag = false + + # check which limit was reached and adjust the currentStepSize for the next cycle + if cycle < settings.approxLevel+1 + if drivingCourse[end][:s] + s_braking > CS[:s_exit] + testFlag && println("in CS",CS[:id]," coasting cycle",cycle," case: s +s_braking=", drivingCourse[end][:s],"+",s_braking," = ",drivingCourse[end][:s] +s_braking," > s_exit=",CS[:s_exit]) # for testing + currentStepSize = settings.stepSize / 10.0^cycle + + elseif drivingCourse[end][:s] > nextPointOfInterest + testFlag && println("in CS",CS[:id]," coasting cycle",cycle," case: s=", drivingCourse[end][:s]," > nextPointOfInterest=",nextPointOfInterest) # for testing + + if settings.stepVariable == :distance + currentStepSize = nextPointOfInterest - drivingCourse[end-1][:s] + else + currentStepSize = settings.stepSize / 10.0^cycle + end + + elseif drivingCourse[end][:v] < CS[:v_exit] # TODO: if accelereation and coasting functions will be combined this case is only for coasting + testFlag && println("in CS",CS[:id]," coasting cycle",cycle," case: v=", drivingCourse[end][:v]," < v_exit=", CS[:v_exit]) # for testing + if settings.stepVariable == velocity + currentStepSize = drivingCourse[end-1][:v] - CS[:v_exit] + else + currentStepSize = settings.stepSize / 10.0^cycle + end + elseif drivingCourse[end][:v] > CS[:v_peak] + testFlag && println("in CS",CS[:id]," coasting cycle",cycle," case: v=", drivingCourse[end][:v]," > v_peak=", CS[:v_peak]) # for testing + if settings.stepVariable == velocity + currentStepSize = CS[:v_peak] - drivingCourse[end-1][:v] + else + currentStepSize = settings.stepSize / 10.0^cycle + end + elseif drivingCourse[end][:s] + s_braking == CS[:s_exit] + testFlag && println("in CS",CS[:id]," coasting cycle",cycle," case: s +s_braking=", drivingCourse[end][:s],"+",s_braking," = ",drivingCourse[end][:s] +s_braking," == s_exit=",CS[:s_exit]) # for testing + break + + elseif drivingCourse[end][:v] == CS[:v_exit] + testFlag && println("in CS",CS[:id]," coasting cycle",cycle," case: v=", drivingCourse[end][:v]," == v_exit=", CS[:v_exit]) # for testing + break + + elseif drivingCourse[end][:s] == nextPointOfInterest + testFlag && println("in CS",CS[:id]," coasting cycle",cycle," case: s =", drivingCourse[end][:s]," > nextPointOfInterest=",nextPointOfInterest) # for testing + break + + else + # TODO: not needed. just for testing + error("ERROR at coasting until braking section: With the step variable ",settings.stepVariable," the while loop will be left although v CS[:s_exit] + # delete last data point because it went to far + pop!(drivingCourse) + pop!(BS[:dataPoints]) + + # conditions for the next for cycle + # brakingStartReached = true + pointOfInterestReached = false + targetSpeedReached = false + + elseif drivingCourse[end][:v] > CS[:v_peak] # if the train gets to fast it has to brake # TODO: if accelereation and coasting functions will be combined this case is different for coasting and also the order of if cases is different + # delete last data point because it went to far + pop!(drivingCourse) + pop!(BS[:dataPoints]) + + # conditions for the next for cycle + brakingStartReached = false + pointOfInterestReached = false + # targetSpeedReached = true + + elseif drivingCourse[end][:s] > nextPointOfInterest + drivingCourse[end][:s] = nextPointOfInterest # round s down to nextPointOfInterest + drivingCourse[end][:Δs] = drivingCourse[end][:s] - drivingCourse[end-1][:s] + else + # do nothing for example for drivingCourse[end][:s] + s_braking == CS[:s_exit] + end + end + end #for + end #while + + stateFlags[:speedLimitReached] = false + + # calculate the accumulated coasting section information + merge!(BS, Dict(:length => drivingCourse[end][:s] - BS[:s_entry], # total length (in m) + :s_exit => drivingCourse[end][:s], # last position (in m) + :t => drivingCourse[end][:t] - drivingCourse[BS[:dataPoints][1]][:t], # total running time (in s) + :E => drivingCourse[end][:E] - drivingCourse[BS[:dataPoints][1]][:E], # total energy consumption (in Ws) + :v_exit => drivingCourse[end][:v])) # exit speed (in m/s))) + + CS[:t] = CS[:t] + BS[:t] # total running time (in s) + CS[:E] = CS[:E] + BS[:E] # total energy consumption (in Ws) + + merge!(CS[:behaviorSections], Dict(:coasting=>BS)) + end + + # set state flags + stateFlags[:endOfCSReached] = endOfCSReached + stateFlags[:brakingStartReached] = brakingStartReached + stateFlags[:tractionDeficit] = drivingCourse[end][:F_T] < drivingCourse[end][:F_R] + stateFlags[:resistingForceNegative] = drivingCourse[end][:F_R] < 0 + stateFlags[:error] = !(endOfCSReached || brakingStartReached || stateFlags[:tractionDeficit] || previousSpeedLimitReached || targetSpeedReached) + + return (CS, drivingCourse, stateFlags) +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}) + # conditions for braking section + targetSpeedReached = drivingCourse[end][:v] <= CS[:v_exit] + endOfCSReached = drivingCourse[end][:s] >= CS[:s_exit] || stateFlags[:endOfCSReached] + + # use the conditions for the braking section + if !targetSpeedReached && !endOfCSReached + BS = createBehaviorSection("braking", drivingCourse[end][:s], drivingCourse[end][:v], drivingCourse[end][:i]) + drivingCourse[end][:behavior] = BS[:type] + + while !targetSpeedReached && !endOfCSReached + currentStepSize = settings.stepSize # initialize the step size that can be reduced near intersections + nextPointOfInterest = getNextPointOfInterest(CS[:pointsOfInterest], drivingCourse[end][:s]) + pointOfInterestReached = drivingCourse[end][:s] >= nextPointOfInterest + + 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 !targetSpeedReached && !endOfCSReached && !pointOfInterestReached + # 03/09 old: while drivingCourse[end][:v] > CS[:v_exit] && !targetSpeedReached && drivingCourse[end][:s] < CS[:s_exit] && drivingCourse[end][:s] < nextPointOfInterest + # traction effort and resisting forces (in N): + calculateForces!(drivingCourse[end], CSs, CS[:id], BS[:type], train, settings.massModel) + + # acceleration (in m/s^2): + 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 + # create empty data point and set it for the values of s_exit and v_exit + push!(drivingCourse, createDataPoint()) + drivingCourse[end][:i] = drivingCourse[end-1][:i]+1 + drivingCourse[end][:behavior] = BS[:type] + push!(BS[:dataPoints], drivingCourse[end][:i]) + recalculateLastBrakingPoint!(drivingCourse, CS[:s_exit], CS[:v_exit]) + else + # create the next data point + push!(drivingCourse, moveAStep(drivingCourse[end], settings.stepVariable, currentStepSize, CS[:id])) + drivingCourse[end][:behavior] = BS[:type] + push!(BS[:dataPoints], drivingCourse[end][:i]) + end + #println(drivingCourse[end][:i],". s=",drivingCourse[end][:s]," s_exit=", CS[:s_exit]," v_exit=", CS[:v_exit]," v=",drivingCourse[end][:v]) + + # conditions for the next while cycle + pointOfInterestReached = drivingCourse[end][:s] >= nextPointOfInterest + endOfCSReached = drivingCourse[end][:s] >= CS[:s_exit] + targetSpeedReached = drivingCourse[end][:v] <= CS[:v_exit] + end # while + + # check which limit was reached and adjust the currentStepSize for the next cycle + # TODO: is there a better way than rounding like in the following? + if cycle < settings.approxLevel+1 + if drivingCourse[end][:v] < CS[:v_exit] + if settings.stepVariable == velocity + currentStepSize = drivingCourse[end-1][:v] - CS[:v_exit] + else + currentStepSize = settings.stepSize / 10.0^cycle + end + elseif drivingCourse[end][:s] > nextPointOfInterest + if settings.stepVariable == :distance + currentStepSize = nextPointOfInterest - drivingCourse[end-1][:s] + else + currentStepSize = settings.stepSize / 10.0^cycle + end + elseif drivingCourse[end][:v] == CS[:v_exit] && drivingCourse[end][:s] == CS[:s_exit] + break + elseif drivingCourse[end][:v] == CS[:v_exit] + recalculateLastBrakingPoint!(drivingCourse, CS[:s_exit], CS[:v_exit]) + endOfCSReached = true + # println(" with a=", drivingCourse[end-1][:a]) # for testing + break + elseif drivingCourse[end][:s] == CS[:s_exit] + # println("during braking section in CS",CS[:id],": rounding v down from ", drivingCourse[end][:v] ," to ", CS[:v_exit]) # for testing + recalculateLastBrakingPoint!(drivingCourse, CS[:s_exit], CS[:v_exit]) + targetSpeedReached = true + # println(" with a=", drivingCourse[end-1][:a]) # for testing + break + elseif drivingCourse[end][:s] == nextPointOfInterest + break + end + + # delete last data point for recalculating the last step with reduced step size + pop!(drivingCourse) + pop!(BS[:dataPoints]) + + # conditions for the next for cycle + pointOfInterestReached = false + endOfCSReached = false + targetSpeedReached = false + + else # if the level of approximation is reached + if drivingCourse[end][:v] < 0.0 + # TODO: drivingCourse[end][:v] < CS[:v_exit] should be enough + # reset last point with setting v=v_exit + # println("during braking section in CS",CS[:id],": rounding v up from ", drivingCourse[end][:v] ," to ", CS[:v_exit]) # for testing + recalculateLastBrakingPoint!(drivingCourse, CS[:s_exit], 0.0) + endOfCSReached = true + break + elseif drivingCourse[end][:s] > CS[:s_exit] + # println("during braking section in CS",CS[:id],": rounding s down from ", drivingCourse[end][:s] ," to ", CS[:s_exit]) # for testing + # recalculateLastBrakingPoint!(drivingCourse, CS[:s_exit], CS[:v_exit]) + drivingCourse[end][:s] = CS[:s_exit] + break + elseif drivingCourse[end][:s] > nextPointOfInterest + drivingCourse[end][:s] = nextPointOfInterest # round s down to nextPointOfInterest + drivingCourse[end][:Δs] = drivingCourse[end][:s] - drivingCourse[end-1][:s] + break + elseif drivingCourse[end][:v] == CS[:v_exit] && drivingCourse[end][:s] == CS[:s_exit] + break + elseif drivingCourse[end][:v] < CS[:v_exit] + # reset last point with setting v=v_exit + # println("during braking section in CS",CS[:id],": rounding s up from ", drivingCourse[end][:s] ," to ", CS[:s_exit]) # for testing + recalculateLastBrakingPoint!(drivingCourse, CS[:s_exit], CS[:v_exit]) + endOfCSReached = true + break + elseif drivingCourse[end][:v] == CS[:v_exit] + # println("during braking section in CS",CS[:id],": rounding s up from ", drivingCourse[end][:s] ," to ", CS[:s_exit]) # for testing + recalculateLastBrakingPoint!(drivingCourse, CS[:s_exit], CS[:v_exit]) + endOfCSReached = true + break + elseif drivingCourse[end][:s] == CS[:s_exit] + # println("during braking section in CS",CS[:id],": rounding v down from ", drivingCourse[end][:v] ," to ", CS[:v_exit]) # for testing + recalculateLastBrakingPoint!(drivingCourse, CS[:s_exit], CS[:v_exit]) + targetSpeedReached = true + break + else + # do nothing for example for drivingCourse[end][:s]==nextPointOfInterest + end + end + end #for + end #while + + # calculate the accumulated coasting section information + merge!(BS, Dict(:length => drivingCourse[end][:s] - BS[:s_entry], # total length (in m) + :s_exit => drivingCourse[end][:s], # last position (in m) + :t => drivingCourse[end][:t] - drivingCourse[BS[:dataPoints][1]][:t], # total running time (in s) + :E => drivingCourse[end][:E] - drivingCourse[BS[:dataPoints][1]][:E], # total energy consumption (in Ws) + :v_exit => drivingCourse[end][:v])) # exit speed (in m/s))) + + CS[:t] = CS[:t] + BS[:t] # total running time (in s) + CS[:E] = CS[:E] + BS[:E] # total energy consumption (in Ws) + + merge!(CS[:behaviorSections], Dict(:braking=>BS)) + end # else: return the characteristic section without a braking section + + # set state flags + 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 + stateFlags[:error] = !(endOfCSReached) + calculateForces!(drivingCourse[end], CSs, CS[:id], "default", train, settings.massModel) + stateFlags[:resistingForceNegative] = drivingCourse[end][:F_R] < 0 + + return (CS, drivingCourse, stateFlags) +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}) + 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) + :t => 0.0, # total running time (in s) + :E => 0.0, # total energy consumption (in Ws) + :s_exit => drivingCourse[end][:s], # last position (in m) + :v_exit => drivingCourse[end][:v])) # exit speed (in m/s))) + drivingCourse[end][:behavior] = BS[:type] + + # traction effort and resisting forces (in N) + calculateForces!(drivingCourse[end], CSs, CS[:id], BS[:type], train, settings.massModel) + + merge!(CS[:behaviorSections], Dict(:standstill => BS)) + end # else: return the characteristic section without a standstillSection section + return (CS, drivingCourse) +end #function addStandstill! + +function mergeBehaviorSection!(BSs::Dict, BS::Dict) + if !haskey(BSs, Symbol(BS[:type])) + merge!(BSs, Dict(Symbol(BS[:type]) => BS)) + else + number = "2" + while haskey(BSs, Symbol(BS[:type]*number)) + number = string(parse(Int, number)+1) + end + merge!(BSs, Dict(Symbol(BS[:type]*number) => BS)) + # println("INFO: The ",number,". ",BS[:type]," section has been created. ! ! ! ! ! ! ! ! !") + end + return BSs +end #function mergeBehaviorSection! + +function createBehaviorSection(type::String, s_entry::Real, v_entry::Real, startingPoint::Integer) + BS= Dict(#:type => behavior, # type of behavior section: breakFree, clearing, accelerating, cruising, diminishing, coasting, braking or standstill + :type => type, # type of behavior section: "breakFree", "clearing", "accelerating", "cruising", "downhillBraking", "diminishing", "coasting", "braking" or "standstill" + :length => 0.0, # total length (in m) + :s_entry => s_entry, # first position (in m) + :s_exit => 0.0, # last position (in m) + :t => 0.0, # total running time (in s) + :E => 0.0, # total energy consumption (in Ws) + :v_entry => v_entry, # entry speed (in m/s) + :v_exit => 0.0, # exit speed (in m/s) + :dataPoints => [startingPoint]) # list of identifiers of the containing data points starting with the initial point + return BS +end #function createBehaviorSection + +""" +a data point is the smallest element of the driving course. One step of the step approach is between two data points +""" +function createDataPoint() + dataPoint = Dict(:i => 0, # identifier and counter variable of the dricing course + :behavior => "", # type of behavior section the data point is part of ("breakFree", "clearing", "accelerating", "cruising", "diminishing", "coasting", "braking" or "standstill") + # a data point which is the last point of one behavior section and the first point of the next behavior section will be attached to the latter + :s => 0.0, # position (in m) + :Δs => 0.0, # step size (in m) + :t => 0.0, # point in time (in s) + :Δt => 0.0, # step size (in s) + :v => 0.0, # velocity (in m/s) + :Δv => 0.0, # step size (in m/s) + :a => 0.0, # acceleration (in m/s^2) + :W => 0.0, # mechanical work (in Ws) + :ΔW => 0.0, # mechanical work in this step (in Ws) + :E => 0.0, # energy consumption (in Ws) + :ΔE => 0.0, # energy consumption in this step (in Ws) + :F_T => 0.0, # tractive effort (in N) + :F_R => 0.0, # resisting force (in N) + :R_path => 0.0, # path resistance (in N) + :R_train => 0.0, # train resistance (in N) + :R_traction => 0.0, # traction unit resistance (in N) + :R_wagons => 0.0) # set of wagons resistance (in N) + return dataPoint +end #function createDataPoint + +function recalculateLastBrakingPoint!(drivingCourse, s_target, v_target) + currentPoint = drivingCourse[end] + previousPoint = drivingCourse[end-1] + # set s and v + currentPoint[:s] = s_target # position (in m) + currentPoint[:v] = v_target # velocity (in m/s) + currentPoint[:Δs] = currentPoint[:s] - previousPoint[:s] # step size (in m) + currentPoint[:Δv] = currentPoint[:v] - previousPoint[:v] # step size (in m/s) + + # 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]) +# 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) + + currentPoint[:ΔW] = 0.0 # mechanical work in this step (in Ws) + currentPoint[:W] = previousPoint[:W] + currentPoint[:ΔW] # mechanical work (in Ws) + currentPoint[:ΔE] = currentPoint[:ΔW] # energy consumption in this step (in Ws) + currentPoint[:E] = previousPoint[:E] + currentPoint[:ΔE] # energy consumption (in Ws) +end #function recalculateLastBrakingPoint diff --git a/src/calc.jl b/src/calc.jl new file mode 100644 index 0000000..3ba2662 --- /dev/null +++ b/src/calc.jl @@ -0,0 +1,160 @@ +#!/usr/bin/env julia +# -*- coding: UTF-8 -*- +# __julia-version__ = 1.7.2 +# __author__ = "Max Kannenberg" +# __copyright__ = "2020-2022" +# __license__ = "ISC" + +# Calculate the driving dynamics of a train run on a path with special settings with information from the corresponding YAML files with the file paths `trainDirectory`, `pathDirectory`, `settingsDirectory`. + +""" + trainRun(train::Dict, path::Dict, settings::Settings) + +Calculate the driving dynamics of a train run on a path with special settings with information from the corresponding dictionaries `train`, `path`, `settings`. + +# Examples +```julia-repl +julia> trainRun(trainDict, pathDict) +todo !!! +``` +""" +function trainRun(trainInput::Dict, pathInput::Dict, 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 && !haskey(path, :pointsOfInterest) + train = copy(trainInput) + path = copy(pathInput) + + # check the input data + (train, path) = checkAndSetInput!(train, path, settings) + settings.outputDetail == :everything && println("The input has been checked.") + + # prepare the input data + movingSection = determineCharacteristics(path, train, settings) + settings.outputDetail == :everything && println("The moving section has been prepared.") + + # calculate the train run for oparation mode "minimum running time" + (movingSection, drivingCourse) = calculateMinimumRunningTime!(movingSection, settings, train) + settings.outputDetail == :everything && println("The driving course for the shortest running time has been calculated.") + + # accumulate data and create an output dictionary + output = createOutput(train, settings, path, movingSection, drivingCourse) + + return output +end # function trainRun + +# calculate a train run focussing on using the minimum possible running time +function calculateMinimumRunningTime!(movingSection::Dict, settings::Settings, train::Dict) + CSs::Vector{Dict} = movingSection[:characteristicSections] + + if settings.massModel == :homogeneous_strip && settings.stepVariable == speed + println("WARNING: ! ! ! TrainRun.jl doesn't work reliably for the mass model homogeneous strip with step size v in m/s. The calculation time can be extremely high when calcutlating paths with steep gradients ! ! !") + end + + startingPoint=createDataPoint() + startingPoint[:i]=1 + startingPoint[:s]=CSs[1][:s_entry] + calculateForces!(startingPoint, CSs, 1, "default", train, settings.massModel) # traction effort and resisting forces (in N) + drivingCourse::Vector{Dict} = [startingPoint] # List of data points + + for csId in 1:length(CSs) + CS = CSs[csId] + # for testing + if drivingCourse[end][:s] != CS[:s_entry] + println("ERROR: In CS", csId," the train run starts at s=",drivingCourse[end][:s]," and not s_entry=",CS[:s_entry]) + end + if drivingCourse[end][:v] > CS[:v_entry] + println("ERROR: In CS", csId," the train run ends with v=",drivingCourse[end][:v]," and not with v_entry=",CS[:v_entry]) + 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]) + calculateForces!(drivingCourse[end], CSs, CS[:id], "default", train, settings.massModel) # tractive effort and resisting forces (in N) + + previousSpeedLimitReached = false + stateFlags = Dict(:endOfCSReached => drivingCourse[end][:s] > CS[:s_exit], + :brakingStartReached => drivingCourse[end][:s] + s_braking == CS[:s_exit], + :tractionDeficit => drivingCourse[end][:F_T] < drivingCourse[end][:F_R], # or add another flag for equal forces? + :resistingForceNegative => drivingCourse[end][:F_R] < 0.0, + :previousSpeedLimitReached => false, #speedLimitReached, # check already at this position? + :speedLimitReached => drivingCourse[end][:v] > CS[:v_limit], + :error => false) + + # determine the behavior sections for this characteristic section. It has to be at least one of those BS: "breakFree", "clearing", "accelerating", "cruising", "diminishing", "coasting", "braking" or "standstill") + while !stateFlags[:endOfCSReached] # s < s_exit + if !stateFlags[:brakingStartReached] # s+s_braking < s_exit + if !stateFlags[:tractionDeficit] + if drivingCourse[end][:F_T] > drivingCourse[end][:F_R] && drivingCourse[end][:v] == 0.0 + (CS, drivingCourse, stateFlags) = addBreakFreeSection!(CS, drivingCourse, stateFlags, settings, train, CSs) + + elseif stateFlags[:previousSpeedLimitReached] + (CS, drivingCourse, stateFlags) = addClearingSection!(CS, drivingCourse, stateFlags, settings, train, CSs) + + elseif drivingCourse[end][:F_T] > drivingCourse[end][:F_R] && !stateFlags[:speedLimitReached] + (CS, drivingCourse, stateFlags) = addAcceleratingSection!(CS, drivingCourse, stateFlags, settings, train, CSs) + + elseif drivingCourse[end][:F_T] == drivingCourse[end][:F_R] && !stateFlags[:speedLimitReached] + # cruise only one step + if settings.stepVariable == :distance + s_cruising = settings.stepSize + 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? + 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_cruising = CS[:s_exit] - drivingCourse[end][:s] - s_braking + + if s_cruising > 0.0 + (CS, drivingCourse, stateFlags) = addCruisingSection!(CS, drivingCourse, stateFlags, s_cruising, settings, train, CSs, "downhillBraking") + else + stateFlags[:brakingStartReached] = true + 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_cruising = CS[:s_exit] - drivingCourse[end][:s] - s_braking + + if s_cruising > 0.0 # TODO: define a minimum cruising length? + (CS, drivingCourse, stateFlags) = addCruisingSection!(CS, drivingCourse, stateFlags, s_cruising, settings, train, CSs, "cruising") + else + stateFlags[:brakingStartReached] = true + end + else + error() + end + elseif stateFlags[:tractionDeficit] + (CS, drivingCourse, stateFlags) = addDiminishingSection!(CS, drivingCourse, stateFlags, settings, train, CSs) + + else + error() + end + else#if !stateFlags[:endOfCSReached] # s < s_exit + (CS, drivingCourse, stateFlags) = addBrakingSection!(CS, drivingCourse, stateFlags, settings, train, CSs) + #else + # error() + end + end + #if s == s_exit + # standstill + #end + + + # for testing: + if drivingCourse[end][:s] != CS[:s_exit] + println("ERROR: In CS", csId," the train run ends at s=",drivingCourse[end][:s]," and not s_exit=",CS[:s_exit]) + end + if drivingCourse[end][:v] > CS[:v_exit] + println("ERROR: In CS", csId," the train run ends with v=",drivingCourse[end][:v]," and not with v_exit=",CS[:v_exit]) + end + end #for + + (CSs[end], drivingCourse) = addStandstill!(CSs[end], drivingCourse, settings, train, CSs) + + movingSection[:t] = drivingCourse[end][:t] # total running time (in s) + movingSection[:E] = drivingCourse[end][:E] # total energy consumption (in Ws) + + return (movingSection, drivingCourse) +end #function calculateMinimumRunningTime diff --git a/src/characteristics.jl b/src/characteristics.jl new file mode 100644 index 0000000..4dd83c2 --- /dev/null +++ b/src/characteristics.jl @@ -0,0 +1,241 @@ +#!/usr/bin/env julia +# -*- coding: UTF-8 -*- +# __julia-version__ = 1.7.2 +# __author__ = "Max Kannenberg" +# __copyright__ = "2020-2022" +# __license__ = "ISC" + +## create a moving section and its containing characteristic sections with secured braking, accelerating and cruising behavior +function determineCharacteristics(path::Dict, train::Dict, settings::Settings) + movingSection = createMovingSection(path, train[:v_limit]) + movingSection = secureBrakingBehavior!(movingSection, train[:a_braking]) + movingSection = secureAcceleratingBehavior!(movingSection, settings, train) + #movingSection = secureCruisingBehavior!(movingSection, settings, train) + + return movingSection +end #function determineCharacteristics + +## create a moving section containing characteristic sections +function createMovingSection(path::Dict, v_trainLimit::Real) + # this function creates and returns a moving section dependent on the paths attributes + + s_entry = path[:sections][1][:s_start] # first position (in m) + s_exit = path[:sections][end][:s_end] # last position (in m) + pathLength = s_exit - s_entry # total length (in m) + + CSs=Vector{Dict}() + s_csStart=s_entry + csId=1 + for row in 2:length(path[:sections]) + previousSection = path[:sections][row-1] + currentSection = path[:sections][row] + speedLimitIsDifferent = min(previousSection[:v_limit], v_trainLimit) != min(currentSection[:v_limit], v_trainLimit) + pathResistanceIsDifferent = previousSection[:f_Rp] != currentSection[:f_Rp] + if speedLimitIsDifferent || pathResistanceIsDifferent + # 03/09 old: if min(previousSection[:v_limit], v_trainLimit) != min(currentSection[:v_limit], v_trainLimit) || previousSection[:f_Rp] != currentSection[:f_Rp] + push!(CSs, createCharacteristicSection(csId, s_csStart, previousSection, min(previousSection[:v_limit], v_trainLimit), path)) + s_csStart = currentSection[:s_start] + csId = csId+1 + end #if + end #for + push!(CSs, createCharacteristicSection(csId, s_csStart, path[:sections][end], min(path[:sections][end][:v_limit], v_trainLimit), path)) + + movingSection= Dict(:id => 1, # identifier # if there is more than one moving section in a later version of this tool the id should not be constant anymore + :length => pathLength, # total length (in m) + :s_entry => s_entry, # first position (in m) + :s_exit => s_exit, # last position (in m) + :t => 0.0, # total running time (in s) + :E => 0.0, # total energy consumption (in Ws) + :characteristicSections => CSs) # list of containing characteristic sections + + return movingSection +end #function createMovingSection + + +## create a characteristic section for a path section. A characteristic section is a part of the moving section. It contains behavior sections. +function createCharacteristicSection(id::Integer, s_entry::Real, section::Dict, v_limit::Real, path::Dict) + # Create and return a characteristic section dependent on the paths attributes + characteristicSection= Dict(:id => id, # identifier + :s_entry => s_entry, # first position (in m) + :s_exit => section[:s_end], # last position (in m) + :length => section[:s_end] -s_entry, # total length (in m) + :r_path => section[:f_Rp], # path resistance (in ‰) + :behaviorSections => Dict(), # list of containing behavior sections + :t => 0.0, # total running time (in s) + :E => 0.0, # total energy consumption (in Ws) + :v_limit => v_limit, # speed limit (in m/s) + # initializing :v_entry, :v_peak and :v_exit with :v_limit + :v_peak => v_limit, # maximum reachable speed (in m/s) + :v_entry => v_limit, # maximum entry speed (in m/s) + :v_exit => v_limit) # maximum exit speed (in m/s) + + # list of positions of every point of interest (POI) in this charateristic section for which data points should be calculated + s_exit = characteristicSection[:s_exit] + pointsOfInterest = Vector{Real}() + if haskey(path, :pointsOfInterest) + for POI in path[:pointsOfInterest] + if s_entry < POI && POI < s_exit + push!(pointsOfInterest, POI) + end + end + end + push!(pointsOfInterest, s_exit) # s_exit has to be the last POI so that there will always be a POI to campare the current position with + + merge!(characteristicSection, Dict(:pointsOfInterest => pointsOfInterest)) + + return characteristicSection +end #function createCharacteristicSection + +## define the intersection velocities between the characterisitc sections to secure braking behavior +function secureBrakingBehavior!(movingSection::Dict, a_braking::Real) + # this function limits the entry and exit velocity of the characteristic sections to secure that the train stops at the moving sections end + CSs = movingSection[:characteristicSections] + + csId = length(CSs) + followingCSv_entry = 0.0 # the exit velocity of the last characteristic section is 0.0 m/s + while csId >= 1 + CS = CSs[csId] + + CS[:v_exit] = min(CS[:v_limit], followingCSv_entry) + + v_entryMax = calcBrakingStartVelocity(CS[:v_exit], a_braking, CS[:length]) + + CS[:v_entry] = min(CS[:v_limit], v_entryMax) + CS[:v_peak] = CS[:v_entry] + + + # reset the characteristic section (CS), delete behavior sections (BS) that were used during the preperation for setting v_entry, v_peak and v_exit + CS[:behaviorSections] = Dict() + CS[:E] = 0.0 + CS[:t] = 0.0 + + followingCSv_entry = CS[:v_entry] + csId = csId - 1 + end #while + return movingSection +end #function secureBrakingBehavior! + +## define the intersection velocities between the characterisitc sections to secure accelerating behavior +function secureAcceleratingBehavior!(movingSection::Dict, settings::Settings, train::Dict) + # 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] + + CSs[1][:v_entry] = 0.0 # the entry velocity of the first characteristic section is 0.0 m/s + startingPoint = createDataPoint() + startingPoint[:i] = 1 + + previousCSv_exit = CSs[1][:v_entry] + for CS in CSs + CS[:v_entry] = min(CS[:v_entry], previousCSv_exit) + startingPoint[:s] = CS[:s_entry] + startingPoint[:v] = CS[:v_entry] + calculateForces!(startingPoint, CSs, CS[:id], "accelerating", train, settings.massModel) # traction effort and resisting forces (in N) + acceleratingCourse::Vector{Dict} = [startingPoint] # List of data points + + if CS[:v_entry] < CS[:v_peak] + # conditions for entering the accelerating phase + stateFlags = Dict(:endOfCSReached => false, + :brakingStartReached => false, + :tractionDeficit => false, + :resistingForceNegative => false, + :previousSpeedLimitReached => false, + :speedLimitReached => false, + :error => false, + :usedForDefiningCharacteristics => true) # because usedForDefiningCharacteristics == true the braking distance will be ignored during securing the accelerating phase + v_peak = CS[:v_entry] + (CS, acceleratingCourse, stateFlags) = addBreakFreeSection!(CS, acceleratingCourse, stateFlags, settings, train, CSs) + while !stateFlags[:speedLimitReached] && !stateFlags[:endOfCSReached] + if !stateFlags[:tractionDeficit] + if !stateFlags[:previousSpeedLimitReached] + (CS, acceleratingCourse, stateFlags) = addAcceleratingSection!(CS, acceleratingCourse, stateFlags, settings, train, CSs) # this function changes the acceleratingCourse + + elseif stateFlags[:previousSpeedLimitReached] + (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] + 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 + end + end + v_peak = max(v_peak, acceleratingCourse[end][:v]) + end + +# CS[:v_peak] = max(CS[:v_entry], acceleratingCourse[end][:v]) + CS[:v_peak] = v_peak + CS[:v_exit] = min(CS[:v_exit], CS[:v_peak], acceleratingCourse[end][:v]) + else #CS[:v_entry] == CS[:v_peak] + # v_exit stays the same + end #if + + previousCSv_exit = CS[:v_exit] + + # reset the characteristic section (CS), delete behavior sections (BS) that were used during the preperation for setting v_entry, v_peak and v_exit + CS[:behaviorSections] = Dict() + CS[:E] = 0.0 + CS[:t] = 0.0 + end #for + + return movingSection +end #function secureAcceleratingBehavior! + + +#= +## define the intersection velocities between the characterisitc sections to secure cruising behavior +function secureCruisingBehavior!(movingSection::Dict, settings::Settings, train::Dict) + # limit the exit velocity of the characteristic sections in case that the train cruises in every section at v_peak + CSs = movingSection[:characteristicSections] + + startingPoint = createDataPoint() + startingPoint[:i] = 1 + + previousCSv_exit = CSs[1][:v_entry] + + for CS in CSs + # conditions for entering the cruising phase + stateFlags = Dict(:endOfCSReached => false, + :brakingStartReached => false, + :tractionDeficit => false, + :resistingForceNegative => false, + :previousSpeedLimitReached => false, + :speedLimitReached => false, + :error => false, + :usedForDefiningCharacteristics => true) + + CS[:v_entry] = min(CS[:v_entry], previousCSv_exit) + + startingPoint[:s] = CS[:s_entry] + startingPoint[:v] = CS[:v_peak] + cruisingCourse::Vector{Dict} = [startingPoint] # List of data points + + while !stateFlags[:endOfCSReached] #&& s_cruising > 0.0 + if !stateFlags[:tractionDeficit] + s_cruising = CS[:s_exit] - cruisingCourse[end][:s] + if !stateFlags[:resistingForceNegative]# cruisingCourse[end][:F_R] >= 0 + (CS, cruisingCourse, stateFlags) = addCruisingSection!(CS, cruisingCourse, stateFlags, s_cruising, settings, train, CSs, "cruising") # this function changes the cruisingCourse + else + (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] + 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 + end + end + end + + CS[:v_exit] = min(CS[:v_exit], cruisingCourse[end][:v]) + + previousCSv_exit = CS[:v_exit] + + # reset the characteristic section (CS), delete behavior sections (BS) that were used during the preperation for setting v_entry, v_peak and v_exit + CS[:behaviorSections] = Dict() + CS[:E] = 0.0 + CS[:t] = 0.0 + end #for + + return movingSection +end #function secureCruisingBehavior! +=# diff --git a/src/export.jl b/src/export.jl new file mode 100644 index 0000000..7113866 --- /dev/null +++ b/src/export.jl @@ -0,0 +1,212 @@ +#!/usr/bin/env julia +# -*- coding: UTF-8 -*- +# __julia-version__ = 1.7.2 +# __author__ = "Max Kannenberg" +# __copyright__ = "2020-2022" +# __license__ = "ISC" + +function exportToCsv(runningTime::AbstractFloat, settings::Settings) + createCsvFile(runningTime, settings) + + return true +end + +function exportToCsv(dataPointsToExport::Vector{Dict}, settings::Settings) + createCsvFile(dataPointsToExport, settings) + + return true +end + +function exportToCsv(output::Dict) + if output[:settings][:outputFormat] == "CSV" + pathName = output[:path][:name] + trainName = output[:train][:name] + + if output[:settings][:operationModeMinimumRunningTime] == true + operationMode = "minimum running time" + if output[:settings][:outputDetail] == "points of interest" + dataPointsToExport = output[:pointsOfInterestMinimumRunningTime] + else + dataPointsToExport = output[:drivingCourseMinimumRunningTime] + end + createCsvFile(dataPointsToExport, operationMode, pathName, trainName, output[:settings]) + end + if output[:settings][:operationModeMinimumEnergyConsumption] == true + operationMode = "minimum energy consumption" + if output[:settings][:outputDetail] == "points of interest" + dataPointsToExport = output[:pointsOfInterestMinimumEnergyConsumption] + else + dataPointsToExport = output[:drivingCourseMinimumEnergyConsumption] + end + createCsvFile(dataPointsToExport, operationMode, pathName, trainName, output[:settings]) + end + return true + end + return false +end #function exportToCsv + +function createCsvFile(runningTime::AbstractFloat, settings::Settings) + # create DataFrame with running time information + df = DataFrame(column1=["t (in s)", runningTime]) + + # save DataFrame as a CSV-file at outputDir + date = Dates.now() + dateString = Dates.format(date, "yyyy-mm-dd_HH.MM.SS") + + csvFilePath = settings[:outputDir]*"/"*dateString*"_RunningTime.csv" + + CSV.write(csvFilePath, df, header=false) + println("The output CSV file has been created at ",csvFilePath) + + return true +end #function createCsvFile + + +function createCsvFile(dataPointsToExport::Vector{Dict}, settings::Settings) + outputDetail = settings[:outputDetail] + + header = ["i", "behavior", "Δs (in m)", "s (in m)", "Δt (in s)","t (in s)","Δv (in m/s)","v (in m/s)","F_T (in N)","F_R (in N)","R_path (in N)","R_train (in N)","R_traction (in N)","R_wagons (in N)", "ΔW (in Ws)","W (in Ws)","ΔE (in Ws)","E (in Ws)","a (in m/s^2)"] + columnSymbols = [:i, :behavior, :Δs, :s, :Δt, :t, :Δv, :v, :F_T, :F_R, :R_path, :R_train, :R_traction, :R_wagons, :ΔW, :W, :ΔE, :E, :a] + + allColumns = Array{Any,1}[] + for column in 1:length(header) + currentColumn = Any[] + push!(currentColumn, header[column]) + for point in dataPointsToExport + push!(currentColumn, point[columnSymbols[column]]) + end + push!(allColumns, currentColumn) + end # for + + + # combine the columns in a data frame and saving it as a CSV-file at outputDir + if outputDetail == "driving course" || outputDetail == "points of interest" + df = DataFrame(c1=allColumns[1], c2=allColumns[2],c3=allColumns[3], c4=allColumns[4], c5=allColumns[5], c6=allColumns[6], c7=allColumns[7], c8=allColumns[8], c9=allColumns[9], c10=allColumns[10], c11=allColumns[11], c12=allColumns[12], c13=allColumns[13], c14=allColumns[14], c15=allColumns[15], c16=allColumns[16], c17=allColumns[17], c18=allColumns[18], c19=allColumns[19]) + + else + println("") + end + + date = Dates.now() + dateString=Dates.format(date, "yyyy-mm-dd_HH.MM.SS") + csvFilePath=settings[:outputDir]*"/"*dateString*"_DataPoints.csv" + CSV.write(csvFilePath, df, header=false) + println("The output CSV file has been created at ",csvFilePath) + + return true +end #function createCsvFile + + +function createCsvFile(dataPointsToExport::Vector{Dict}, operationMode::String, pathName::String, trainName::String, settings::Settings) + outputDetail = settings[:outputDetail] + + massModel = settings.massModel + stepVariable = settings.stepVariable + stepSize = string(settings.stepSize) + + # create accumulated data block + accumulatedData = Array{Any, 1}[] + push!(accumulatedData, ["i", "behavior", "Δs (in m)", "s (in m)", "Δt (in s)","t (in s)","Δv (in m/s)","v (in m/s)","F_T (in N)","F_R (in N)","R_path (in N)","R_train (in N)","R_traction (in N)","R_wagons (in N)", "ΔW (in Ws)","W (in Ws)","ΔE (in Ws)","E (in Ws)","a (in m/s^2)"]) # push header to accumulatedData + for point in dataPointsToExport + row = [point[:i], point[:behavior], point[:Δs], point[:s], point[:Δt], point[:t], point[:Δv], point[:v], point[:F_T], point[:F_R], point[:R_path], point[:R_train], point[:R_traction], point[:R_wagons], point[:ΔW], point[:W], point[:ΔE], point[:E], point[:a]] + push!(accumulatedData, row) # push row to accumulatedData + end + + #create information block + allColumns=Array{Any,1}[] + push!(allColumns, ["path name", "train name", "operation mode", "mass model", "step variable", "step size", ""]) + push!(allColumns, [pathName, trainName, operationMode, massModel, stepVariable, stepSize, ""]) + for column in 3:length(accumulatedData[1]) + push!(allColumns, ["", "", "", "", "", "", ""]) + end # for + + # add driving data to the array + header = accumulatedData[1] + for column in 1:length(accumulatedData[1]) + push!(allColumns[column], header[column]) + for row in accumulatedData[2:end] + push!(allColumns[column], row[column]) + end + end # for + + # combine the columns in a data frame and saving it as a CSV-file at outputDir + df = DataFrame(c1=allColumns[1], c2=allColumns[2],c3=allColumns[3], c4=allColumns[4], c5=allColumns[5], c6=allColumns[6], c7=allColumns[7], c8=allColumns[8], c9=allColumns[9], c10=allColumns[10], c11=allColumns[11], c12=allColumns[12], c13=allColumns[13], c14=allColumns[14], c15=allColumns[15], c16=allColumns[16], c17=allColumns[17], c18=allColumns[18], c19=allColumns[19]) + + date = Dates.now() + dateString=Dates.format(date, "yyyy-mm-dd_HH.MM.SS") + if operationMode == "minimum running time" + csvFilePath=settings[:outputDir]*"/"*dateString*"_MinimumRunningTime.csv" + elseif operationMode == "minimum energy consumption" + csvFilePath=settings[:outputDir]*"/"*dateString*"_MinimumEnergyConsumption.csv" + else + # should not be possible + end + CSV.write(csvFilePath, df, header=false) + println("The output CSV file has been created for ",operationMode," at ",csvFilePath) + + return true +end #function createCsvFile + + + +#= +function createCsvFile(movingSection::Dict, dataPointsToExport::Vector{Dict}, operationMode::String, pathName::String, trainName::String, settings::Settings) + outputDetail = settings[:outputDetail] + + massModel = settings.massModel + stepVariable = settings.stepVariable + stepSize = string(settings.stepSize) + + # create accumulated data block + accumulatedData = Array{Any, 1}[] + if outputDetail == "minimal" + push!(accumulatedData, ["s (in m)", "t (in s)","E (in Ws)"]) # push header to accumulatedData + row = [movingSection[:length], movingSection[:t], movingSection[:E]] + push!(accumulatedData, row) # push row to accumulatedData + elseif outputDetail == "driving course" || outputDetail == "points of interest" + push!(accumulatedData, ["i", "behavior", "Δs (in m)", "s (in m)", "Δt (in s)","t (in s)","Δv (in m/s)","v (in m/s)","F_T (in N)","F_R (in N)","R_path (in N)","R_train (in N)","R_traction (in N)","R_wagons (in N)", "ΔW (in Ws)","W (in Ws)","ΔE (in Ws)","E (in Ws)","a (in m/s^2)"]) # push header to accumulatedData + for point in dataPointsToExport + row = [point[:i], point[:behavior], point[:Δs], point[:s], point[:Δt], point[:t], point[:Δv], point[:v], point[:F_T], point[:F_R], point[:R_path], point[:R_train], point[:R_traction], point[:R_wagons], point[:ΔW], point[:W], point[:ΔE], point[:E], point[:a]] + push!(accumulatedData, row) # push row to accumulatedData + end + end + + #create information block + allColumns=Array{Any,1}[] + push!(allColumns, ["path name", "train name", "operation mode", "mass model", "step variable", "step size", ""]) + push!(allColumns, [pathName, trainName, operationMode, massModel, stepVariable, stepSize, ""]) + for column in 3:length(accumulatedData[1]) + push!(allColumns, ["", "", "", "", "", "", ""]) + end # for + + # add driving data to the array + header = accumulatedData[1] + for column in 1:length(accumulatedData[1]) + push!(allColumns[column], header[column]) + for row in accumulatedData[2:end] + push!(allColumns[column], row[column]) + end + end # for + + # combine the columns in a data frame and saving it as a CSV-file at outputDir + if outputDetail == "minimal" + df = DataFrame(c1=allColumns[1], c2=allColumns[2],c3=allColumns[3]) + elseif outputDetail=="driving course" || outputDetail == "points of interest" + df = DataFrame(c1=allColumns[1], c2=allColumns[2],c3=allColumns[3], c4=allColumns[4], c5=allColumns[5], c6=allColumns[6], c7=allColumns[7], c8=allColumns[8], c9=allColumns[9], c10=allColumns[10], c11=allColumns[11], c12=allColumns[12], c13=allColumns[13], c14=allColumns[14], c15=allColumns[15], c16=allColumns[16], c17=allColumns[17], c18=allColumns[18], c19=allColumns[19]) + end + + date = Dates.now() + dateString=Dates.format(date, "yyyy-mm-dd_HH.MM.SS") + if operationMode == "minimum running time" + csvFilePath=settings[:outputDir]*"/"*dateString*"_MinimumRunningTime.csv" + elseif operationMode == "minimum energy consumption" + csvFilePath=settings[:outputDir]*"/"*dateString*"_MinimumEnergyConsumption.csv" + else + # should not be possible + end + CSV.write(csvFilePath, df, header=false) + println("The output CSV file has been created for ",operationMode," at ",csvFilePath) + + return true +end #function createCsvFile +=# diff --git a/src/formulary.jl b/src/formulary.jl new file mode 100644 index 0000000..1be57a6 --- /dev/null +++ b/src/formulary.jl @@ -0,0 +1,226 @@ +#!/usr/bin/env julia +# -*- coding: UTF-8 -*- +# __julia-version__ = 1.7.2 +# __author__ = "Max Kannenberg" +# __copyright__ = "2022" +# __license__ = "ISC" + +######################### +## literature the driving dynamics equations are based on: +## +## @incollection{Bruenger:2014, % Chapter 4 +## author = {Brünger, Olaf and Dahlhaus, Elias}, +## year = {2014}, +## title = {Running Time Estimation}, +## pages = {65--90}, +## booktitle = {Railway Timetabling \& Operations.}, +## editora = {Hansen, Ingo A.}, +## editorb = {Pachl, Jörn}, +## isbn = {978-3-777-10462-1}, +## publisher = {Eurailpress DVV Media Group}, +## } +## @Book{Wende:2003, +## author = {Wende, Dietrich}, +## date = {2003}, +## title = {Fahrdynamik des Schienenverkehrs}, +## isbn = {978-3-322-82961-0}, +## publisher = {Springer-Verlag}, +## } +######################### + +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 + +#TODO: replace the ? ? ? +""" + calcTractionUnitResistance(v, train) + +Calculate the vehicle resistance for the traction unit of the `train` dependend on the velocity `v`. + +... +# Arguments +- `v::AbstractFloat`: the current velocity in m/s. +- `train::Dict`: ? ? ? +... + +# Examples +```julia-repl +julia> calcTractionUnitResistance(30.0, ? ? ?) +? ? ? +``` +""" +function calcTractionUnitResistance(v::AbstractFloat, train::Dict) + # 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_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) + 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 ‰ +end #function calcTractionUnitResistance + +""" +TODO +calculate and return the wagons vehicle resistance dependend on the velocity +""" +function calcWagonsResistance(v::AbstractFloat, train::Dict) + # 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_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) + return F_R_wagons +end #function calcWagonsResistance + +function calcForceFromCoefficient(f_R::Real, m::Real) + # equation is based on [Wende:2003, page 8] + + # f_R: specific resistance (in ‰) + # m: vehicle's mass (in kg) + + F_R = f_R /1000 *m *g # Resisting Force (in N) # /1000 because of the unit ‰ + return F_R +end #function calcForceFromCoefficient + +function calcAcceleration(F_T::Real, F_R::Real, m_train::Real, ξ_train::Real) + # equation is based on [Bruenger:2014, page 72] with a=dv/dt + + # F_T: tractive effort (in N) + # F_R: resisting forces (in N) + # m_train: train's mass (in kg) + # ξ_train: train's rotation mass factor (without unit) + + a = (F_T - F_R) /m_train /ξ_train # acceleration (in m/s) + return a +end #function calcAcceleration + +function calc_Δs_with_Δt(Δt::Real, a_prev::Real, v_prev::Real) + # equation is based on [Wende:2003, page 37] + + # Δt: time step (in s) + # a_prev: acceleration from previous data point + # v_prev: velocitiy from previous data point + Δs = Δt * (2*v_prev + Δt*a_prev) /2 # step size (in m) + return Δs +end #function calc_Δs_with_Δt + +function calc_Δs_with_Δv(Δv::Real, a_prev::Real, v_prev::Real) + # equation is based on [Wende:2003, page 37] + + # Δv: velocity step (in m/s) + # a_prev: acceleration from previous data point + # v_prev: velocitiy from previous data point + Δs = ((v_prev + Δv)^2 - v_prev^2)/2/a_prev # step size (in m) + return Δs +end #function calc_Δs_with_Δv + +function calc_Δt_with_Δs(Δs::Real, a_prev::Real, v_prev::Real) + # equation is based on [Wende:2003, page 37] + + # Δs: distance step (in m) + # a_prev: acceleration from previous data point + # v_prev: velocitiy from previous data point + + Δt = sign(a_prev) *sqrt((v_prev /a_prev)^2 + 2 *Δs /a_prev) - v_prev /a_prev # step size (in m/s) + return Δt +end #function calc_Δt_with_Δs + +function calc_Δt_with_Δv(Δv::Real, a_prev::Real) + # equation is based on [Wende:2003, page 37] + + # Δv: velocity step (in m/s) + # a_prev: acceleration from previous data point + Δt = Δv /a_prev # step size (in s) + return Δt +end #function calc_Δt_with_Δv + +function calc_Δt_with_constant_v(Δs::Real, v::Real) + # equation is based on [Wende:2003, page 37] + + # Δs: distance step (in m) + # v: constant velocity (in m/s) + Δt = Δs /v # step size (in s) + return Δt +end #function calc_Δt_with_constant_v + +function calc_Δv_with_Δs(Δs::Real, a_prev::Real, v_prev::Real) + # equation is based on [Wende:2003, page 37] + + # Δs: distance step (in m) + # a_prev: acceleration from previous data point + # v_prev: velocitiy from previous data point + Δv = sqrt(v_prev^2 + 2*Δs*a_prev) - v_prev # step size (in m/s) + return Δv +end #function calc_Δv_with_Δs + +function calc_Δv_with_Δt(Δt::Real, a_prev::Real) + # equation is based on [Wende:2003, page 37] + + # Δt: time step (in s) + # a_prev: acceleration from previous data point + Δv = Δt * a_prev # step size (in m/s) + return Δv +end #function calc_Δv_with_Δt + +function calc_ΔW(F_T_prev::Real, Δs::Real) + # equation is based on [Wende:2003, page 17] + + # F_T_prev: tractive force from previous data point + # Δs: distance step + ΔW = F_T_prev * Δs # mechanical work in this step (in Ws) + return ΔW +end #function calc_ΔW + +function calc_ΔE(ΔW::Real) + # simplified equation + # TODO! + # ΔW: mechanical work in this step (in Ws) + ΔE = ΔW # energy consumption in this step (in Ws) + return ΔE +end #function calc_ΔW + +function calcBrakingDistance(v_start::Real, v_end::Real, a_braking::Real) + # equation is based on [Wende:2003, page 37] + + # v_start: velocity at the start of braking (in m/s) + # v_end: target velocity at the end of braking (in m/s) + # a_braking: constant braking acceleration (in m/s^2) + s_braking = (v_end^2 - v_start^2) /2 /a_braking # braking distance (in m) + # TODO: also possible: calc_Δs_with_Δv(v_end-v_start, a_braking, v_start) +# return max(0.0, ceil(s_braking, digits=approxLevel)) # ceil is used to be sure that the train stops at s_exit in spite of rounding errors + return max(0.0, ceil(s_braking, digits=approxLevel +1)) # ceil is used to be sure that the train stops at s_exit in spite of rounding errors +end #function calcBrakingDistance + +function calcBrakingStartVelocity(v_end::Real, a_braking::Real, s_braking::Real) + # equation is based on [Wende:2003, page 37] + + # v_end: target velocity at the end of braking (in m/s) + # a_braking: constant braking acceleration (in m/s^2) + # s_braking: braking distance (in Ws) + v_start = sqrt(v_end^2 - 2*a_braking *s_braking) # braking start velocity (in m/s) +# return floor(v_start, digits=approxLevel) + return floor(v_start, digits=approxLevel +1) +end #function calcBrakingStartVelocity + +function calcBrakingAcceleration(v_start::Real, v_end::Real, s_braking::Real) + # equation is based on [Wende:2003, page 37] + + # v_start: braking start velocity (in m/s) + # v_end: target velocity at the end of braking (in m/s) + # s_braking: braking distance (in Ws) + a_braking = (v_end^2 - v_start^2) /2 /s_braking # constant braking acceleration (in m/s^2) + return a_braking +end #function calcBrakingAcceleration diff --git a/src/import.jl b/src/import.jl new file mode 100644 index 0000000..aae95af --- /dev/null +++ b/src/import.jl @@ -0,0 +1,35 @@ +#!/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 new file mode 100644 index 0000000..a21c12e --- /dev/null +++ b/src/output.jl @@ -0,0 +1,106 @@ +#!/usr/bin/env julia +# -*- coding: UTF-8 -*- +# __julia-version__ = 1.7.2 +# __author__ = "Max Kannenberg" +# __copyright__ = "2020-2022" +# __license__ = "ISC" + +function createOutput(train::Dict, settings::Settings, path::Dict, movingSection::Dict, drivingCourse::Vector{Dict}) + if settings.outputDetail == :running_time + output = movingSection[:t] # TODO: or use drivingCourse[end][:t] + + elseif settings.outputDetail == :points_of_interest + # add points of interest + if haskey(path, :pointsOfInterest) + output = Vector{Dict}() + POI = 1 + i = 1 + while POI <= length(path[:pointsOfInterest]) && i <= drivingCourse[end][:i] + if path[:pointsOfInterest][POI] == drivingCourse[i][:s] + push!(output, drivingCourse[i]) + POI = POI+1 + end + i = i+1 + end + end + + elseif settings.outputDetail == :driving_course + output = drivingCourse + + elseif settings.outputDetail == :everything + output = Dict{Symbol,Any}() + merge!(output, Dict(:train => train, :path => path, :settings => settings)) + + + # add moving section and driving courses + if settings[:operationModeMinimumRunningTime] == true + merge!(output, Dict(:movingSectionMinimumRunningTime => movingSection, + :drivingCourseMinimumRunningTime => drivingCourse)) + elseif settings[:operationModeMinimumEnergyConsumption] == true + merge!(output, Dict(:movingSectionMinimumEnergyConsumption => movingSection, + :drivingCourseMinimumEnergyConsumption => drivingCourse)) + end + + # add points of interest + if haskey(path, :pointsOfInterest) + pointsOfInterest = Vector{Dict}() + POI = 1 + i = 1 + while POI <= length(path[:pointsOfInterest]) && i <= drivingCourse[end][:i] + if path[:pointsOfInterest][POI] == drivingCourse[i][:s] + push!(pointsOfInterest, drivingCourse[i]) + POI = POI+1 + end + i = i+1 + end + + if settings[:operationModeMinimumRunningTime] == true + merge!(output, Dict(:pointsOfInterestMinimumRunningTime => pointsOfInterest)) + elseif settings[:operationModeMinimumEnergyConsumption] == true + merge!(output, Dict(:pointsOfInterestMinimumEnergyConsumption => pointsOfInterest)) + end + end + else + output = nothing + end + return output +end + +#= +function createOutputDict(train::Dict, settings::Settings, path::Dict, movingSection::Dict, drivingCourse::Vector{Dict}) + outputDict = Dict{Symbol,Any}() + merge!(outputDict, Dict(:train => train, :path => path, :settings => settings)) + + + # add moving section and driving courses + if settings[:operationModeMinimumRunningTime] == true + merge!(outputDict, Dict(:movingSectionMinimumRunningTime => movingSection, + :drivingCourseMinimumRunningTime => drivingCourse)) + elseif settings[:operationModeMinimumEnergyConsumption] == true + merge!(outputDict, Dict(:movingSectionMinimumEnergyConsumption => movingSection, + :drivingCourseMinimumEnergyConsumption => drivingCourse)) + end + + # add points of interest + if haskey(path, :pointsOfInterest) + pointsOfInterest = Vector{Dict}() + POI = 1 + i = 1 + while POI <= length(path[:pointsOfInterest]) && i <= drivingCourse[end][:i] + if path[:pointsOfInterest][POI] == drivingCourse[i][:s] + push!(pointsOfInterest, drivingCourse[i]) + POI = POI+1 + end + i = i+1 + end + + if settings[:operationModeMinimumRunningTime] == true + merge!(outputDict, Dict(:pointsOfInterestMinimumRunningTime => pointsOfInterest)) + elseif settings[:operationModeMinimumEnergyConsumption] == true + merge!(outputDict, Dict(:pointsOfInterestMinimumEnergyConsumption => pointsOfInterest)) + end + end + + return outputDict +end # function createOutputDict +=# diff --git a/src/types.jl b/src/types.jl new file mode 100644 index 0000000..3419b44 --- /dev/null +++ b/src/types.jl @@ -0,0 +1,799 @@ +#!/usr/bin/env julia +# -*- coding: UTF-8 -*- +# __julia-version__ = 1.7.2 +# __author__ = "Max Kannenberg, Martin Scheidt" +# __copyright__ = "2022" +# __license__ = "ISC" + +""" + Settings(file) + +Settings is a datastruture for calculation context. +The function Settings() will create a set of settings for the train run calculation. +`file` is optinal may be used to load settings in the YAML format. + +# Example +```jldoctest +julia> my_settings = Settings() # will generate default settings +Settings(mass_point, :distance, 20, 3, running_time, julia_dict, ".") +``` +""" +struct Settings + + massModel::Symbol # model type of train mass ":mass_point" or ":homogeneous_strip". + stepVariable::Symbol # variable of the linear multistep method: ":distance", ":time" or ":velocity". + stepSize::Real # step size, unit depends on stepVariable - :distance in meter, time in seconds and velocity in meter/second. + approxLevel::Int # value for approximation; used when rounding or interating. + outputDetail::Symbol # single Float() ":running_time", Array() of ":points_of_interest", + # complete Array() ":driving_course", or Dict() ":everything". + outputFormat::Symbol # output as ":julia_dict" or as ":csv". + outputDir::String # if outputFormat is not ":julia_dict". + + ## constructor + function Settings(file="DEFAULT") + + ## default values + massModel = :mass_point + stepVariable = :distance + stepSize = 20 + approxLevel = 3 + outputDetail = :running_time + outputFormat = :julia_dict + outputDir = "." + + if file != "DEFAULT" + ## JSON schema for YAML-file validation + schema = Schema("""{ + "properties": { + "massModel": { + "description": "type of train model", + "type": "string", + "enum": [ "mass_point", "homogeneous_strip" ] + }, + "stepVariable": { + "description": "variable of the linear multistep method", + "type": "string", + "enum": [ "distance", "time", "velocity" ] + }, + "stepSize": { + "description": "step size acording to stepVariable", + "type": "number", + "exclusiveMinimum": 0 + }, + "outputDetail": { + "description": "Selecting the detail of the result", + "type": "string", + "enum": [ "running_time", "points_of_interest", "driving_course", "everything" ] + }, + "outputFormat": { + "description": "Output format", + "type": "string", + "enum": [ "julia_dict", "csv" ] + }, + "outputDir": { + "description": "Path for the CSV export", + "type": "string" + } + } + }""") + + settings = YAML.load(open(file))["settings"] + + ## validate the loaded file + try + validate(schema, settings) + catch err + println("Could not load settings file $file.\n Format is not recognized - using default as fall back.") + settings = Dict() + end + + ## set the variables if they exist 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 + haskey(settings, "approxLevel") ? approxLevel = settings["approxLevel"] : nothing + haskey(settings, "outputDetail") ? outputDetail = Symbol(settings["outputDetail"]) : nothing + haskey(settings, "outputFormat") ? outputFormat = Symbol(settings["outputFormat"]) : nothing + haskey(settings, "outputDir") ? outputDir = settings["outputDir"] : nothing + end + + new(massModel, stepVariable, stepSize, approxLevel, outputDetail, outputFormat, outputDir) + + end #function Settings() # constructor + +end #struct Settings + +""" +Read the input information from YAML files for train, path and settings, save it in different dictionaries and return them. +""" +function checkAndSetInput!(train::Dict, path::Dict, settings::Settings) + checkAndSetTrain!(train) + checkAndSetPath!(path) + + if settings.outputDetail == :points_of_interest && !haskey(path, :pointsOfInterest) + throw(DomainError(settings.outputDetail, "INFO at checking the input for settings and path:\n + settings[:outputDetail] is 'points of interest' but the path does not for pointsOfInterest.")) + end + return (train, path) +end #function checkAndSetInput! + +""" +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::Dict) + # 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::Dict) + # 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::Dict, 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 + 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::Dict) + # read the section starting positions and corresponding information + if haskey(path, :pointsOfInterest) + if path[:pointsOfInterest] != nothing + pointsOfInterest = path[:pointsOfInterest] + + 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[:pointsOfInterest] = copiedPOIs + + else + println("INFO at checking the input for the path: The key pointsOfInterest exists but without values.") + delete!(path, :pointsOfInterest) + 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