Calculating your own routes

Hi Commanders,

Here is a little piece of software which calculates "optimized" routes. Code is written in julia (http://julialang.org/) which is free and available for all major platforms.

I used it to calculate the following rare goods round trip route:

Code:
"System"	"Station"	"distance"	"Item"	"Price"	"DistancesToNextSystems"
"Aerial"	"Andrade Legacy"	180.0	"Edan Apples of Aerial"	621.0	"  61,  72,  94,  89,  70, 102, 109, 145, 171, 144"
"Jaroua"	"Mccool City"	143.0	"Jaroua Rice"	385.0	"  45,  72,  64, 126, 161, 170, 204, 229, 199, 212"
"Irukama"	"Blaauw City"	323.0	"Giant Irukama Snails"	1810.0	"  67,  88, 141, 159, 171, 209, 234, 191, 203, 234"
"Karsuki Ti"	"West Market"	28.0	"Karsuki Locusts"	915.0	"  50, 148, 166, 187, 230, 255, 225, 238, 269, 303"
"Goman"	"Gustav Sporer Port"	291.0	"Goman Yaupon Coffee"	1451.0	" 134, 170, 186, 224, 249, 230, 245, 274, 309, 248"
"Quechua"	"Crown Ring"	120.0	"Albino Quechua Mammoth Meat"	2538.0	"  64,  62,  92, 116, 119, 133, 156, 203, 143, 124"
"Utgaroar"	"Fort Klarix"	171.0	"Utgaroar Millennial Eggs"	1795.0	"  35,  80,  99,  88, 102, 128, 179, 135,  95,  75"
"Tanmark"	"Cassie-L-Peia"	414.0	"Tanmark Tranquil Tea"	1814.0	"  47,  69,  65,  79, 102, 154, 107,  78,  71,  93"
"Witchhaul"	"Hornby Terminal"	219.0	"Witchhaul Kobe Beef"	4520.0	"  26,  63,  71,  78, 132,  90,  86,  96, 135, 145"
"Momus Reach"	"Tartarus Point"	413.0	"Momus Bog Spaniel"	1836.0	"  73,  77,  74, 128,  98,  99, 112, 157, 169, 166"
"George Pantazis"	"Zamka Platform"	45.0	"Pantaa Prayer Sticks"	1827.0	"  15,  45,  91,  58,  27,  49, 110, 119, 103,  86"
"Zeessze"	"Nicollier Hanger"	489.0	"Zeessze Ant Grub Glue"	380.0	"  33,  77,  55,  32,  59, 122, 130, 111,  89, 134"
"Bast"	"Hart Station"	202.0	"Bast Snake Gin"	1089.0	"  58,  57,  63,  92, 155, 160, 140, 111, 154, 154"
"Ethgreze"	"Bloch Station"	351.0	"Ethgreze Tea Buds"	3308.0	"  69,  93, 125, 188, 184, 155, 114, 140, 140, 141"
"AZ Cancri"	"Fisher Station"	16.0	"Az Cancri Formula 42"	6442.0	"  60,  89, 144, 132, 112,  75, 121, 121, 123, 124"
"Epsilon Indi"	"Mansfield Orbiter"	143.0	"Indi Bourbon"	978.0	"  32,  96, 100,  79,  62, 109, 109, 110, 112, 103"
"Tiolce"	"Gordon Terminal"	158.0	"Tiolce Waste 2 Paste"	1153.0	"  65,  79,  64,  69, 113, 113, 113, 115, 103, 114"
"Phiagre"	"Greeboski\'s Outpost"	85.0	"Giant Verrix"	6552.0	"  50,  66, 104, 136, 136, 136, 137, 113,  74, 119"
"Baltah\'Sine"	"Baltha\'sine Station"	356.0	"Baltha\'sine Vacuum Krill"	825.0	"  40,  81, 109, 109, 110, 110,  70,  58,  91,  74"
"CD-75 661"	"Kirk Dock"	339.0	"CD-75 Kitten Brand Coffee"	2373.0	"  47,  72,  72,  73,  73,  68,  97, 128, 106, 158"
"Shinrarta Dezhra (permit)"	"Jameson Memorial"	347.0	"Waters of Shintara"	3710.0	"  54,  54,  56,  57,  63, 130, 164, 148, 202, 214"
"Leesti"	"George Lucas"	257.0	"Leestian Evil Juice"	462.0	"   0,   3,   4,  97, 165, 185, 161, 221, 240, 195"
"Leesti"	"George Lucas"	257.0	"Azure Milk"	4164.0	"   3,   4,  97, 165, 185, 161, 221, 240, 195, 184"
"Diso"	"Shifnalport"	284.0	"Diso Ma Corn"	340.0	"   4,  99, 166, 186, 161, 221, 240, 197, 184, 177"
"Lave"	"Lave Station"	302.0	"Lavian Brandy"	3543.0	"  98, 166, 184, 160, 220, 239, 197, 186, 178, 192"
"Arouca"	"Shipton Orbital"	410.0	"Arouca Conventula Sweets"	1203.0	"  97, 121, 121, 177, 176, 124, 143, 128, 141, 162"
"Aerial"	"Andrade Legacy"	180.0	"Edan Apples of Aerial"	621.0	"  61,  72,  94,  89,  70, 102, 109, 145, 171, 144"

The last column indicates the distance to the next systems. So, if your cargo bay is full, just count the distances and find your next target system.

This is the julia 0.4 code but you can just download the zip folder attached including rare goods system source file.

Code:
#After installing julia you need to install this package once
#  start julia and type at the command line:
# Pkg.add("DataFrames")
#
#For 3D route  plotting with https://plot.ly/
# Pkg.add("Plotly")

##########################################################################
# Configure parameteres start here
#

#path to source file "Rare Commodities List.csv"
# Windows examples:
#  path="d:\\Temp\\TSP"
#  path="c:\\Data\\Oli\\Eigene Spiele\\EliteDangerous"
# Unix examples:
#  path="/home/username/TSP"
#  path="/data"
path="."

#maximum distance of station allowed to consider system
maxDistance=500

#number of cores to use
ncores=4

#starting system, as we calculate a round trip, this is the first and 
# the last system in the result list
startSystemName="Aerial"

#
# end of configuration
##########################################################################

using DataFrames
addprocs(ncores)
cd(path)

# read raw data
raw = readtable("RareCommoditiesList.txt")

#
# prefilter the set of systems
#
#only use system when station distance is within some range
raw=raw[raw[:,:distance] .< maxDistance,:]
#no illegal rares
raw=raw[raw[:,:illegal] .== 0,:]
# dont consider Zaonce as you only get 1 egg
raw=raw[raw[:,:System] .!= "Zaonce",:]

function createDistances(distances,minDistances,minDistancesIndices)
    numberSystems=size(distances)[1]
    for col = 2:numberSystems
        for row = 1:(col-1)
            distance=round(sqrt((raw[col,:x]-raw[row,:x])^2+(raw[col,:y]-raw[row,:y])^2+(raw[col,:z]-raw[row,:z])^2))
            distances[row,col]=distance
            distances[col,row]=distance
        end
    end
    for col = 1:numberSystems
        minDistancesIndices[col,:]=sortperm(distances[col,:][:])[2:numberSystems]
        minDistances[col,:]=distances[col,minDistancesIndices[col,:][:]]
    end
end

#create distance matrix
numberSystems=size(raw,1)
distances=zeros(Int,(numberSystems,numberSystems))
minDistances=zeros(Int,(numberSystems,numberSystems-1))
minDistancesIndices=zeros(Int,(numberSystems,numberSystems-1))

#remove all systems which do not have at least two near neighbours
createDistances(distances,minDistances,minDistancesIndices)
maxDist=70
while true
    good=[ (minDistances[i,1] < maxDist) && (minDistances[i,2] < maxDist) for i=1:size(minDistances)[1] ]
    newraw=raw[good.==true,:]
    if size(newraw) == size(raw)
        break
    end
    raw=newraw
    numberSystems=size(raw,1)
    distances=zeros(Int,(numberSystems,numberSystems))
    minDistances=zeros(Int,(numberSystems,numberSystems-1))
    minDistancesIndices=zeros(Int,(numberSystems,numberSystems-1))
    createDistances(distances,minDistances,minDistancesIndices)
end

startSystem=find(raw[:System].==startSystemName)
if length(startSystem) != 1
    print(string("Start system \"",startSystemName,"\" not in list:\n"))
    print(raw[:System])
    print(string("\nUse another system as starting point!\n"))
    startSystem=1
    print(string("Setting start system to ",raw[:System][startSystem],"\n"))
end

import Base.pmap
function pmap(f,lst,minDistancesIndices,minDistances)
    np = nprocs()  # determine the number of processes available
    n = length(lst)
    results = cell(n)
    i = 1
    # function to produce the next work item from the queue.
    # in this case it's just an index.
    nextidx() = (idx=i; i+=1; idx)
    @sync begin
        for p=1:np
            if p != myid() || np == 1
                @async begin
                    while true
                        idx = nextidx()
                        if idx > n
                            break
                        end
                        results[idx] = remotecall_fetch(p,(args)->f(args...), Any[lst[idx],minDistancesIndices,minDistances,idx] )
                    end
                end
            end
        end
    end
    results
end

function psplit(lst,ncores)
    maxSize=20000
    np = ncores
    n = length(lst)
    rest=n
    available=np
    indices1=[]
    indices2=[]
    idx1=0
    idx2=0
    size=convert(Int,(ceil(rest/available)))
    nparts=min(np,n)
    if size < maxSize
        while rest>0
            size=convert(Int,(ceil(rest/available)))
            idx1=idx2+1
            idx2=idx1+size-1
            indices1=vcat(indices1,idx1)
            indices2=vcat(indices2,idx2)
            rest=rest-size
            available-=1
        end
    else
        size=maxSize
        nparts=0
        while rest>0
            if rest < size
                size=rest
            end
            idx1=idx2+1
            idx2=idx1+size-1
            indices1=vcat(indices1,idx1)
            indices2=vcat(indices2,idx2)
            rest=rest-size
            nparts+=1
        end
    end
    print(string("Split into ",nparts," parts. Part sizes:\n"))
    results = cell(nparts)
    for p=1:nparts
        idx1=indices1[p]
        idx2=indices2[p]
        print(string(" Part ",p,": size=",idx2-idx1+1," from ",idx1," to ",idx2,"\n"))
        results[p]=lst[idx1:idx2]
    end
    results
end

#
# this function is the core of the algorithm
#   it adds to an existing route one new system N-times
#   => this results in N new routes with one new system
#   Example:
#     Input route is [3,2,6]
#     Result routes are e.g.
#        [3,2,6,1]
#        [3,2,6,4]
#        [3,2,6,7]
#   The numbers here are the indices of the system in DataFrame raw.
#   The number of systems found to add depends on the arrays
#      maxNextDistances
#      maxNextCounts
#

@everywhere function expand(routes,minDistancesIndices,minDistances,p)

    #
    # maxNextDistances and maxNextCounts define the way 
    # the next system is added to the existing systems in a route
    # - both are arrays and must have the same length
    # 
    # maxNextDistances=[70,400]
    # maxNextCounts=[2,2]
    # We start with the first entry in maxNextDistances: 70
    # The first entry in maxNextCounts is 2
    # => find 2 systems which are less than 70 away from the last system in current route
    # If we find at least one => ok, add the systems found to the current route
    #   creating as many new routes as systems found.
    # If we find no system, go to the next index:
    #   maxNextDistances: 400
    #   maxNextCounts: 2
    # => find 2 systems which are less than 400 away from the last system in current route
    # If we find at least one => ok, add the systems found to the current route
    # If we find no system and there is no more entry
    # => the current route can not be expanded, delete it
    #
    # Working examples:
    #   maxNextDistances=[10,20,30,40,50,100,150,400]
    #   maxNextCounts=[1,1,2,3,4,4,5,5]
    #  Took 48 hours to calculate on a 128 GByte workstation, Cores used (ncores) 16
    #
    #   maxNextDistances=[70,400]
    #   maxNextCounts=[2,2]
    #  Takes some minutes on i7, 16GByte Ram, Cores used (ncores) 4
    #
    # Not working:
    #   maxNextDistances=[1000]
    #   maxNextCounts=[numberSystems]
    #  This would calculate the complete Travelling Salesman Problem for all systems
    #  in your starting set. If you have super computer access you may tray with
    #  numberSystems > 12. On my workstation it may work with numberSystems=11, i dont try
    #  it will take to long to wait. We have all together 107 rare systems, try to 
    #  calculate 107! , thats the size of the problem :-)
    #
    maxNextDistances=[70,400]
    maxNextCounts=[2,2]
    n=length(routes)
    newRoutes=[]
    numberSystems=size(minDistancesIndices)[2]
    systemsInRoute=length(routes[1])
    for routeIndex=1:n
        maxNextDistanceIndex=1
        route=routes[routeIndex]
        currentSystem=route[systemsInRoute]
        indices=1:numberSystems
        usableNext=trues(numberSystems)
        usableNext[findin(minDistancesIndices[currentSystem,indices],route)]=false
        indices=indices[usableNext]
        nextSystemsExpand=minDistancesIndices[currentSystem,indices][minDistances[currentSystem,indices] .< maxNextDistances[maxNextDistanceIndex]]
        while length(nextSystemsExpand)==0 && maxNextDistanceIndex < length(maxNextDistances)
            maxNextDistanceIndex+=1
            nextSystemsExpand=minDistancesIndices[currentSystem,indices][minDistances[currentSystem,indices] .< maxNextDistances[maxNextDistanceIndex]]
        end
        nextSystemsExpand=nextSystemsExpand[1:min(maxNextCounts[maxNextDistanceIndex],length(nextSystemsExpand))]
        newRoutes=vcat(newRoutes, [ vcat(route,i) for i=nextSystemsExpand ])
    end
    print(string("  ",p,": expanding ",n," routes finished\n"))
    newRoutes
end

#
# calculate possible routes
#
systemsInRoute=1
currentSystem=startSystem
routes=collect(startSystem)
for system=1:(numberSystems-1)
    print("start\n")
    print(string(system," Number of routes: ",length(routes)," ; length of routes:",systemsInRoute,"\n"))
    proutes=psplit(routes,ncores)
    tic()
    presult=pmap(expand,proutes,minDistancesIndices,minDistances)
    toc()
    newRoutes=[]
    for j=1:length(presult)
        newRoutes=vcat(newRoutes,presult[j])
    end
    if( length(newRoutes) > 0 ) 
        routes=newRoutes
        systemsInRoute+=1
    else
        break
    end
    print(string(system," Number of routes: ",length(routes)," ; length of routes:",systemsInRoute,"\n"))
    print("end\n")
end

routes=[vcat(routes[i],startSystem) for i=1:length(routes)]
routeDistances=[[distances[routes[j][i],routes[j][i+1]] for i=1:(length(routes[j])-1)] for j=1:length(routes)]

#
# three possibilities to find the "best" route:
#

#minimize sum of route distances
shortestRouteIndex=sortperm([sum(routeDistances[j]) for j=1:length(routes)])[1]
shortestRoute=routes[shortestRouteIndex]
raw[shortestRoute,[:System,:Station,:distance,:Item,:Price]]
routeDistances[shortestRouteIndex]

#minimize the standard deviation of route distances
smallestStdRouteIndex=sortperm([std(convert(Array{Int,1},routeDistances[j])) for j=1:length(routes)])[1]
smallestStdRoute=routes[smallestStdRouteIndex]
raw[smallestStdRoute,[:System,:Station,:distance,:Item,:Price]]
routeDistances[smallestStdRouteIndex]

#minimize the maximum distance of route distances
smallestMaxRouteIndex=sortperm([maximum(routeDistances[j]) for j=1:length(routes)])[1]
smallestMaxRoute=routes[smallestMaxRouteIndex]
raw[smallestMaxRoute,[:System,:Station,:distance,:Item,:Price]]
routeDistances[smallestMaxRouteIndex]

#choose a result, I have chosen result smallestStdRoute
usedRoute=smallestStdRoute              #set here
r1=r"\["
r2=r"\]"
maxDistancesToNextSystem=10
allDistances=[ 
    join([ 
        @sprintf("%4s",distances[usedRoute,usedRoute][[(i+1):systemsInRoute;1:(i-1)],i][j])
        for j=1:maxDistancesToNextSystem 
    ],",")
    for i=1:systemsInRoute ]
allDistances=vcat(allDistances,allDistances[1])
result=raw[usedRoute,[:System,:Station,:distance,:Item,:Price]]
result[:DistancesToNextSystems]=allDistances

#print out the result
result
#or write it to a tab separated file:
#writetable("rareRoute.txt",result,separator='\t')



using Plotly
# dont use https, it seems to be broken because of libCURL
Plotly.signin("RexKraemer", "USE_YOUR_OWN_USER_AND_API_KEY",Dict("plotly_domain"=>"http://plot.ly", "plotly_api_domain"=>"http://api.plot.ly/v2"))

usedRoute=smallestStdRoute
plot_result=raw[usedRoute,[:System,:x,:y,:z]]
plot_result[:System]=[x[1:min(100,length(x))] for x=plot_result[:System] ]

trace1=Dict(
"x" => plot_result[:,:x],
"y" => plot_result[:,:y],
"z" => plot_result[:,:z],
"mode" => "lines",
"type" => "scatter3d",
"line" => Dict(
    "color" => "#1f77b4", 
    "width" => 1
)
)

trace2=Dict(
"x" => plot_result[:,:x],
"y" => plot_result[:,:y],
"z" => plot_result[:,:z],
"mode" => "lines+markers+text",
"text" => convert(Array{ASCIIString,1},plot_result[:,:System]),
"textposition" => "top center",
"type" => "scatter3d"
)

data=[trace2]
layout = Dict(
"showlegend" => false,
"title" => "Route",
"margin" => Dict(
    "l" => 0, 
    "r" => 0, 
    "b" => 0, 
    "t" => 0
)
)
response = Plotly.plot(data, Dict("layout" => layout, "filename" => "3d-route", "fileopt" => "overwrite"))

This was just some kind of programming exercise for me, but maybe somebody finds it usefull. Use it, analyze it, play with it, improve it, learn, have fun.

Download the rare goods list which is read in by the program:View attachment RareCommoditiesList.txt

CMDR Rex Kraemer
 
Last edited:
Back
Top Bottom