Skip to content

Commit

Permalink
Merge branch 'main' into redesign
Browse files Browse the repository at this point in the history
  • Loading branch information
gwehrle authored Jul 1, 2024
2 parents 7ed80dd + c1a7755 commit 84f04a7
Show file tree
Hide file tree
Showing 12 changed files with 287 additions and 240 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ jobs:
fail-fast: false
matrix:
version:
- '1.6' # oldest
- '1.8' # current
- '1.8' # oldest
- '1' # current
- 'nightly' # dev
os:
- ubuntu-latest
Expand Down
5 changes: 3 additions & 2 deletions src/TrainRuns.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ include("formulary.jl")
include("calc.jl")
include("behavior.jl")
include("output.jl")
include("utils.jl")

## main function
"""
Expand Down Expand Up @@ -57,14 +58,14 @@ function trainrun(train::Train, path::Path, settings = Settings()::Settings)
@debug "" settings

# prepare the input data
(characteristicSections, poi_positions) = determineCharacteristics(
(characteristicSections, pois) = determineCharacteristics(
path, train, settings)

# calculate the train run with the minimum running time
drivingCourse = calculateMinimumRunningTime(characteristicSections, settings, train)

# accumulate data and create an output dictionary
output = createOutput(settings, drivingCourse, poi_positions)
output = createOutput(settings, drivingCourse, pois)

return output
end
Expand Down
161 changes: 70 additions & 91 deletions src/behavior.jl

Large diffs are not rendered by default.

55 changes: 28 additions & 27 deletions src/calc.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@
function calculateMinimumRunningTime(CSs::Vector{Dict}, settings::Settings, train::Train)
startingPoint = SupportPoint()
startingPoint[:s] = CSs[1][:s_entry]
if !isempty(CSs[1][:pointsOfInterest]) &&
CSs[1][:pointsOfInterest][1][:s] == CSs[1][:s_entry]
startingPoint[:label] = CSs[1][:pointsOfInterest][1][:label]
end

calculateForces!(startingPoint, CSs, 1, "default", train, settings.massModel) # traction effort and resisting forces (in N)
drivingCourse::Vector{Dict} = [startingPoint] # List of support points

Expand Down Expand Up @@ -481,32 +478,38 @@ function getLowestSpeedLimit(
end #function getLowestSpeedLimit

"""
TODO
getNextPointOfInterest(poi_positions, s)
"""
function getNextPointOfInterest(pointsOfInterest::Vector{NamedTuple}, s::Real)
for POI in pointsOfInterest
if POI[:s] > s
return POI
function getNextPoiPosition(poi_positions::Vector{Real}, s::Real)
for position in poi_positions
if position > s
return position
end
end
error("ERROR in getNextPointOfInterest: There is no POI higher than s=", s, " m.")
end #function getNextPointOfInterest

## create vectors with the moving section's points of interest and with the characteristic sections with secured braking and accelerating behavior
"""
determineCharacteristics(path, train, settings)
create vectors with the moving section's points of interest and with the characteristic sections with secured braking and accelerating behavior
"""
function determineCharacteristics(path::Path, train::Train, settings::Settings)
# determine the positions of the points of interest depending on the interesting part of the train (front/rear) and the train's length
poi_positions = []
pointsOfInterest = NamedTuple[]
if !isempty(path.poi)
for POI in path.poi
s_poi = POI[:station]
if POI[:measure] == "rear"
s_poi += train.length
end
push!(pointsOfInterest, (s = s_poi, label = POI[:label]))
push!(poi_positions, s_poi)
end
sort!(pointsOfInterest, by = x -> x[:s])

pois = DataFrame(path.poi)

if size(pois, 1) > 0
# calculate the relevant position on track for pois
# when measure is rear the train length is substracted
pois.s = ifelse.(
pois.measure .== "rear", pois.station .+ train.length, pois.station)

poi_positions = Vector{Any}(pois.s)
sort!(poi_positions)
else
poi_positions = Vector{Any}()
end

# create the characteristic sections of a moving section 'CSs' dependent on the paths attributes
Expand All @@ -527,8 +530,7 @@ function determineCharacteristics(path::Path, train::Train, settings::Settings)
s_csStart,
previousSection,
min(previousSection[:v_limit], train.v_limit),
train.length,
pointsOfInterest
poi_positions
)
)
s_csStart = currentSection[:s_start]
Expand All @@ -540,13 +542,12 @@ function determineCharacteristics(path::Path, train::Train, settings::Settings)
s_csStart,
path.sections[end],
min(path.sections[end][:v_limit], train.v_limit),
train.length,
pointsOfInterest
poi_positions
)
)

# secure that the train is able to brake sufficiently and keeps speed limits
CSs = secureBrakingBehavior!(CSs, train.a_braking, settings.approxLevel)

return (CSs, poi_positions)
return (CSs, pois)
end #function determineCharacteristics
23 changes: 9 additions & 14 deletions src/constructors.jl
Original file line number Diff line number Diff line change
Expand Up @@ -584,8 +584,7 @@ function CharacteristicSection(
s_entry::Real,
section::Dict,
v_limit::Real,
s_trainLength::Real,
MS_poi::Vector{NamedTuple}
poi_positions::Vector
)
# Create and return a characteristic section dependent on the paths attributes
characteristicSection::Dict{Symbol, Any} = Dict(
Expand All @@ -598,19 +597,16 @@ function CharacteristicSection(

# get the list of positions of every point of interest (POI) in this charateristic section for which support points should be calculated from the list of the whole moving section's POI
s_exit = characteristicSection[:s_exit]
CS_poi = NamedTuple[]
if !isempty(MS_poi)
for POI in MS_poi
s_poi = POI[:s]
if s_entry <= s_poi && s_poi <= s_exit
push!(CS_poi, POI)
end
CS_poi_positions = Real[]
for position in poi_positions
if s_entry <= position && position <= s_exit
push!(CS_poi_positions, position)
end
end
if isempty(CS_poi) || CS_poi[end][:s] < s_exit
push!(CS_poi, (s = s_exit, label = "")) # s_exit has to be the last POI so that there will always be a POI to campare the current position with
if isempty(CS_poi_positions) || CS_poi_positions[end] < s_exit
push!(CS_poi_positions, s_exit) # s_exit has to be the last POI so that there will always be a POI to campare the current position with
end
merge!(characteristicSection, Dict(:pointsOfInterest => CS_poi))
merge!(characteristicSection, Dict(:pointsOfInterest => CS_poi_positions))

return characteristicSection
end #function CharacteristicSection
Expand All @@ -631,8 +627,7 @@ function SupportPoint()
: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)
:label => "" # a label for important points
:R_wagons => 0.0 # set of wagons resistance (in N)
)
return supportPoint
end #function SupportPoint
132 changes: 28 additions & 104 deletions src/output.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,125 +33,49 @@ julia> createOutput(settings_poi, drivingCourse_longdistance, pointsOfInterest_p
function createOutput(
settings::Settings,
drivingCourse::Vector{Dict},
poi_positions::Vector{Any}
pois::DataFrame
)
if settings.outputDetail == :running_time
output::Vector{Dict} = [Dict(:t => drivingCourse[end][:t])]
drivingCourse = DataFrame(drivingCourse)

elseif settings.outputDetail == :points_of_interest
# get only the driving course's support points with POI labels
# if there is no point with POI label return the information of departure and arrival (first and last points)
output = Dict[]
if isempty(poi_positions)
push!(output, drivingCourse[1])
push!(output, drivingCourse[end])
else
for supportPoint in drivingCourse
if supportPoint[:s] in poi_positions
push!(output, supportPoint)
end
end
end
output = getOutputByDetail(drivingCourse, pois, settings.outputDetail)

elseif settings.outputDetail == :data_points
# get the driving course's support points where a new behavior section starts and the driving mode changes
output = Dict[]
# the first support point is the first data point
push!(output, drivingCourse[1])

for supportPoint in 2:length(drivingCourse)
if drivingCourse[supportPoint - 1][:behavior] !=
drivingCourse[supportPoint][:behavior]
push!(output, drivingCourse[supportPoint])
end
end

elseif settings.outputDetail == :driving_course
output = drivingCourse
if settings.outputFormat == :vector
return df_2_vector(output)
end

if settings.outputFormat == :dataframe
return createDataFrame(output, settings.outputDetail, settings.approxLevel)
elseif settings.outputFormat == :vector
return output
end
return output
end

"""
createDataFrame(output_vector, outputDetail, approxLevel)
getOutputByDetail(drivingCourse::DataFrame, pois::DataFrame, outputDetail::Symbol)::DataFrame
Create a DataFrame from `output_vector` with `outputDetail` and `approxLevel`.
Filter drivingCourse depending on output detail.
See also [`createOutput`](@ref).
# Arguments
- `output_vector::Vector{Dict}`: the Vector containing all data to be outputted.
- `outputDetail::Symbol`: the detail level the DataFrame is created for.
- `approxLevel::Int`: the number of digits for rounding each Number in the DataFrame.
# Examples
```julia-repl
julia> createDataFrame(vector_pointsOfInterest, detail_data_points, approxLevel_default)
5×11 DataFrame
Row │ label driving_mode s v t a F_T F_R R_path R_traction R_wagons
│ String String Real Real Real Real Real Real Real Real Real
─────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1 │ view_point_1 accelerating 850.0 28.707 54.049 0.331 1.93049e5 36602.1 0.0 9088.56 27513.6
2 │ distant_signal_1 accelerating 1000.0 30.325 59.129 0.294 1.82746e5 43604.7 4344.35 9795.13 29465.2
3 │ main_signal_1 accelerating 2000.0 37.356 88.468 0.185 1.48352e5 60899.4 8688.69 13259.1 38951.5
4 │ main_signal_3 braking 9000.0 27.386 258.578 -0.375 0.0 34522.1 0.0 8537.05 25985.0
5 │ clearing_point_1 braking 9203.37 24.443 266.426 -0.375 0.0 30176.2 0.0 7389.44 22786.8
```
`outputDetail` can be `:running_time`, `:points_of_interest`, `:data_points` or `:driving_course`
"""
function createDataFrame(
output_vector::Vector{Dict},
outputDetail::Symbol,
approxLevel::Int
)
function getOutputByDetail(
drivingCourse::DataFrame, pois::DataFrame, outputDetail::Symbol)::DataFrame
if outputDetail == :running_time
# create a DataFrame with running time information
dataFrame = DataFrame(t = [round(output_vector[end][:t], digits = approxLevel)])
else # :points_of_interest, :data_points or :driving_course
columnSymbols = [
:label, :behavior, :s, :v, :t, :a, :F_T, :F_R, :R_path, :R_traction, :R_wagons]
return DataFrame(t = drivingCourse[end, :t])
elseif outputDetail == :points_of_interest
if size(pois, 1) == 0
output = drivingCourse[[1, end], :]
output.label = ["", ""]
return output
end

allColumns = []
for column in 1:length(columnSymbols)
if typeof(output_vector[1][columnSymbols[column]]) == String
currentStringColumn::Vector{String} = []
for point in output_vector
push!(currentStringColumn, point[columnSymbols[column]])
end
push!(allColumns, currentStringColumn)
elseif typeof(output_vector[1][columnSymbols[column]]) <: Real
currentRealColumn::Vector{Real} = []
for point in output_vector
push!(currentRealColumn, point[columnSymbols[column]])
end
currentRealColumn = round.(currentRealColumn, digits = approxLevel)
push!(allColumns, currentRealColumn)
end
end # for
return rightjoin(drivingCourse, pois, on = :s, order = :left)
elseif outputDetail == :data_points || outputDetail == :driving_course
output = leftjoin(drivingCourse, pois[:, [:s, :label]], on = :s, order = :left)
replace!(output.label, missing => "")

# combine the columns in a data frame
dataFrame = DataFrame(
label = allColumns[1],
driving_mode = allColumns[2],
s = allColumns[3],
v = allColumns[4],
t = allColumns[5],
a = allColumns[6],
F_T = allColumns[7],
F_R = allColumns[8],
R_path = allColumns[9],
R_traction = allColumns[10],
R_wagons = allColumns[11]
)
end
if outputDetail == :data_points
subset!(output, [:behavior] => value_changes)
end

return dataFrame
end #createDataFrame
return output
end
end

function get_loglevel(settings::Settings)::LogLevel
current_logger = global_logger()
Expand Down
59 changes: 59 additions & 0 deletions src/utils.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""
df_2_vector(df::DataFrame)::Vector{Dict}
Convert DataFrame to a Vector of Dicts
# Example
```julia-repl
julia> TrainRuns.df_2_vector(DataFrame(ages=[12,20]))
2-element Vector{Dict}:
Dict(:ages => 12)
Dict(:ages => 20)
```
"""
function df_2_vector(df::DataFrame)::Vector{Dict}
vec = Dict[]

for row in eachrow(df)
dict = Dict(Symbol(cn) => row[cn] for cn in names(df))
push!(vec, dict)
end

return vec
end

"""
value_changes(field::AbstractVector)::Vector{Bool}
Return vector if value changes compared to the one before.
The first element is alwas true.
# Example
```julia-repl
julia> TrainRuns.value_changes(DataFrame(ages=[12,20]))
5-element Vector{Bool}:
1
0
1
0
```
"""
function value_changes(field::AbstractVector)::Vector{Bool}
if (isempty(field))
return Vector{Bool}[]
end

value = first(field)
keep::Vector{Bool} = [true]

for current_value in field[2:end]
if value != current_value
value = current_value
push!(keep, true)
else
push!(keep, false)
end
end

return keep
end
Loading

0 comments on commit 84f04a7

Please sign in to comment.