Skip to content

Commit

Permalink
Assorted runtime fixes introduced in conversion
Browse files Browse the repository at this point in the history
  • Loading branch information
kfsone committed Aug 26, 2014
1 parent c752fd9 commit 42413d1
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 76 deletions.
2 changes: 1 addition & 1 deletion trade.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ def parse_command_line():
ship = tdb.getShip(args.ship)
args.ship = ship
if args.capacity is None: args.capacity = ship.capacity
if args.maxLyPer is None: args.maxLyPer = ship.maxJumpFull
if args.maxLyPer is None: args.maxLyPer = ship.maxLyFull
if args.capacity is None:
raise CommandLineError("Missing '--capacity' or '--ship' argument")
if args.maxLyPer is None:
Expand Down
76 changes: 44 additions & 32 deletions tradecalc.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def __eq__(self, rhs):
return self.gainCr == rhs.gainCr and len(self.jumps) == len(rhs.jumps)

def str(self):
return "%s -> %s" % (self.route[0], self.route[-1])
return "%s -> %s" % (self.route[0].str(), self.route[-1].str())

def detail(self, detail=0):
credits = self.startCr
Expand All @@ -67,7 +67,7 @@ def detail(self, detail=0):
hop = self.hops[i]
hopGainCr, hopTonnes = hop[1], 0
text += " >-> " if i == 0 else " + "
text += "At %s/%s, Buy:" % (route[i].system.str().upper(), route[i].station)
text += "At %s/%s, Buy:" % (route[i].system.name(), route[i].name())
for (item, qty) in sorted(hop[0], key=lambda item: item[1] * item[0].gainCr, reverse=True):
if detail > 1:
text += "\n | %4d x %-30s" % (qty, item.item)
Expand All @@ -89,7 +89,7 @@ def detail(self, detail=0):
text += "\n"
gainCr += hopGainCr

text += " <-< %s gaining %scr => %scr total" % (route[-1], localedNo(gainCr), localedNo(credits + gainCr))
text += " <-< %s gaining %scr => %scr total" % (route[-1].name(), localedNo(gainCr), localedNo(credits + gainCr))
text += "\n"

return text
Expand All @@ -109,7 +109,7 @@ def summary(self):

class TradeCalc(object):
""" Container for accessing trade calculations with common properties """
def __init__(self, tdb, debug=False, capacity=None, maxUnits=None, margin=0.01, unique=False, fit=None):
def __init__(self, tdb, debug=0, capacity=None, maxUnits=None, margin=0.01, unique=False, fit=None):
self.tdb = tdb
self.debug = debug
self.capacity = capacity or 4
Expand Down Expand Up @@ -212,18 +212,18 @@ def getBestTrade(self, src, dst, credits, capacity=None, avoidItems=None, focusI
"""
if not avoidItems: avoidItems = []
if not focusItems: focusItems = []
if self.debug: print("# %s -> %s with %dcr" % (src, dst, credits))
if self.debug: print("# %s -> %s with %dcr" % (src.name(), dst.name(), credits))

if not dst in src.stations:
raise ValueError("%s does not have a link to %s" % (src, dst))
if not dst in src.tradingWith:
raise ValueError("%s does not have a link to %s" % (src.name(), dst.name()))

capacity = capacity or self.capacity
if not capacity:
raise ValueError("zero capacity")

maxUnits = self.maxUnits or capacity

items = src.trades[dst.ID]
items = src.tradingWith[dst]
if avoidItems:
items = [ item for item in items if not item.item in avoidItems ]
if focusItems:
Expand Down Expand Up @@ -253,20 +253,29 @@ def getBestTrade(self, src, dst, credits, capacity=None, avoidItems=None, focusI
return fitFunction(items, credits, capacity, maxUnits)


def getBestHopFrom(self, src, credits, capacity=None, maxJumps=None, maxLy=None, maxLyPer=None):
""" Determine the best trade run from a given station. """
if isinstance(src, str):
src = self.tdb.getStation(src)
hop = None
for (destSys, destStn, jumps, ly, via) in src.getDestinations(maxJumps=maxJumps, maxLy=maxLy, maxLyPer=maxLyPer):
load = self.getBestTrade(src, destStn, credits, capacity=capacity)
if load and (not hop or (load.gainCr > hop.gainCr or (load.gainCr == hop.gainCr and len(jumps) < hop.jumps))):
hop = TradeHop(destSys=destSys, destStn=destStn, load=load.items, gainCr=load.gainCr, jumps=jumps, ly=ly)
return hop
def getBestHopFrom(self, src, credits, capacity=None, maxJumps=None, maxLyPer=None):
"""
Determine the best trade run from a given station.
"""
src = self.tdb.getStation(src)
bestHop = None
for dest in src.getDestinations(maxJumps=maxJumps, maxLyPer=maxLyPer):
load = self.getBestTrade(src, dest.station, credits, capacity=capacity)
if not load:
continue
if bestHop:
if load.gainCr > bestHop.gainCr: continue
if load.gainCr == bestHop.gainCr:
if dest.jumps > bestHop.jumps: continue
if dest.jumps == bestHop.jumps:
if dest.ly >= bestHop.ly:
continue
bestHop = TradeHop(destSys=dest.system, destStn=dest.station, load=load.items, gainCr=load.gainCr, jumps=dest.jumps, ly=dest.ly)
return besthop

def getBestHops(self, routes, credits,
restrictTo=None, avoidItems=None, avoidPlaces=None, maxJumps=None,
maxLy=None, maxJumpsPer=None, maxLyPer=None):
maxJumpsPer=None, maxLyPer=None):
""" Given a list of routes, try all available next hops from each
route. Store the results by destination so that we pick the
best route-to-point for each destination at each step. If we
Expand All @@ -291,24 +300,27 @@ def getBestHops(self, routes, credits,
if jumpLimit <= 0:
continue

for (destSys, destStn, jumps, ly) in src.getDestinations(maxJumps=jumpLimit, maxLy=maxLy, maxLyPer=maxLyPer, avoiding=avoidPlaces):
for dest in src.getDestinations(maxJumps=jumpLimit, maxLyPer=maxLyPer, avoiding=avoidPlaces):
if self.debug:
print("#destSys = %s, destStn = %s, jumps = %s, ly = %s" % (destSys.str(), destStn, "->".join([jump.str() for jump in jumps]), ly))
if not destStn in src.stations:
if self.debug: print("#%s is not in my station list" % destStn)
print("#destSys = %s, destStn = %s, jumps = %s, distLy = %s" % (dest.system.name(), dest.station.name(), "->".join([jump.str() for jump in dest.via]), dest.distLy))
if not dest.station in src.tradingWith:
if self.debug > 2: print("#%s is not in my station list" % dest.station.name())
continue
if restrictTo and destStn != restrictTo:
if self.debug: print("#%s doesn't match restrict %s" % (destStn, restrictTo))
if restrictTo:
if (isinstance(restrictTo, system) and dest.system != restrictTo) \
or (isinstance(restrictTo, station) and dest.station != restrictTo):
if self.debug > 2: print("#%s doesn't match restrict %s" % (dest.station.name(), restrictTo))
continue
if unique and destStn in route.route:
if self.debug: print("#%s is already in the list, not unique" % destStn)
if unique and dest.station in route.route:
if self.debug > 2: print("#%s is already in the list, not unique" % dest.station.name())
continue
trade = self.getBestTrade(src, destStn, startCr, avoidItems=avoidItems)

trade = self.getBestTrade(src, dest.station, startCr, avoidItems=avoidItems)
if not trade:
if self.debug: print("#* No trade")
if self.debug > 2: print("#* No trade")
continue
dstID = destStn.ID

dstID = dest.station.ID
try:
# See if there is already a candidate for this destination
(bestStn, bestRoute, bestTrade, bestJumps, bestLy) = bestToDest[dstID]
Expand All @@ -317,12 +329,12 @@ def getBestHops(self, routes, credits,
newRouteGainCr = route.gainCr + trade[1]
if bestRouteGainCr > newRouteGainCr:
continue
if bestRouteGainCr == newRouteGainCr and bestLy <= ly:
if bestRouteGainCr == newRouteGainCr and bestLy <= dest.distLy:
continue
except KeyError:
# No existing candidate, we win by default
pass
bestToDest[dstID] = [ destStn, route, trade, jumps, ly ]
bestToDest[dstID] = [ dest.station, route, trade, dest.via, dest.distLy ]

result = []
for (dst, route, trade, jumps, ly) in bestToDest.values():
Expand Down
84 changes: 41 additions & 43 deletions tradedb.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,7 @@ class Station(object):

def __init__(self, ID, system, name, lsFromStar=0.0):
self.ID, self.system, self.dbname, self.lsFromStar = ID, system, name, lsFromStar
self.trades = {}
self.stations = []
self.tradingWith = {} # dict[tradingPartnerStation] -> [ available trades ]
system.addStation(self)

def name(self):
Expand All @@ -111,12 +110,10 @@ def addTrade(self, dest, item, itemID, costCr, gainCr):
station and sold for a gain at another.
"""
# TODO: Something smarter.
dstID = dest.ID
if not dstID in self.trades:
self.trades[dstID] = []
self.stations.append(dest)
if not dest in self.tradingWith:
self.tradingWith[dest] = []
trade = Trade(item, itemID, costCr, gainCr)
self.trades[dstID].append(trade)
self.tradingWith[dest].append(trade)

def getDestinations(self, maxJumps=None, maxLyPer=None, avoiding=None):
"""
Expand All @@ -135,9 +132,9 @@ def getDestinations(self, maxJumps=None, maxLyPer=None, avoiding=None):
# The closed list is the list of nodes we've already been to (so
# that we don't create loops A->B->C->A->B->C->...)

Node = namedtuple('Node', [ 'system', 'via', 'dist' ])
Node = namedtuple('Node', [ 'system', 'via', 'distLy' ])

openList = [ OpenNode(self.system, [], 0) ]
openList = [ Node(self.system, [], 0) ]
pathList = { system.ID: Node(system, None, 0.0)
# include avoids so we only have
# to consult one place for exclusions
Expand All @@ -157,39 +154,37 @@ def getDestinations(self, maxJumps=None, maxLyPer=None, avoiding=None):
jumps += 1

for node in ring:
(nodeSys, nodeVia, nodeDist) = (node.system, node.via, node.dist)
for (destSys, destDist) in sys.links.items():
# Range check
dist = nodeDist + destDist
if dist > maxLyPer: continue
for (destSys, destDist) in node.system.links.items():
if destDist > maxLyPer: continue
dist = node.distLy + destDist
try:
prevNode = pathList[destSys.ID]
# If we already have a shorter path, do nothing
if dist >= prevNode.dist: continue
if dist >= prevNode.distLy: continue
except KeyError: pass
# Add to the path list
pathList[destSys.ID] = Node(dest, nodeVia, dist)
pathList[destSys.ID] = Node(destSys, node.via, dist)
# Add to the open list but also include node to the via
# list so that it serves as the via list for all next-hops.
openList += Node(dest, nodeVia + [dest], dist)
openList += [ Node(destSys, node.via + [destSys], dist) ]

DestNode = namedtuple('DestNode', [ 'system', 'station', 'via', 'dist' ])
Destination = namedtuple('Destination', [ 'system', 'station', 'via', 'distLy' ])

destStations = []
# always include the local stations, unless the user has indicated they are
# avoiding this system. E.g. if you're in Chango but you've specified you
# want to avoid Chango...
if not self.system in avoiding:
for station in self.stations:
if not station in avoiding:
destStations += Node(self, station, [], 0.0)
for station in self.system.stations:
if station in self.tradingWith and not station in avoiding:
destStations += [ Destination(self, station, [], 0.0) ]

avoidStations = [ station for station in avoiding if isinstance(station, Station) ]
epsiol = sys.float_info.epsilon
epsilon = sys.float_info.epsilon
for node in pathList.values():
if node.dist > epsilon: # Values indistinguishable from zero are avoidances
if node.distLy > epsilon: # Values indistinguishable from zero are avoidances
for station in node.system.stations:
destStations += Node(node.system, station, node.via, node.dist)
destStations += [ Destination(node.system, station, [self] + node.via + [station], node.distLy) ]

return destStations

Expand All @@ -202,8 +197,9 @@ def str(self):
def __repr__(self):
return '<Station: {}, {}, {}, {}>'.format(self.ID, self.system.name(), self.dbname, self.lsFromStar)

class Ship(namedtuple('Ship', [ 'ID', 'name', 'capacity', 'mass', 'driveRating', 'maxLyEmpty', 'maxLyFull', 'maxSpeed', 'boostSpeed', 'stations' ])):
pass
class Ship(namedtuple('Ship', [ 'ID', 'dbname', 'capacity', 'mass', 'driveRating', 'maxLyEmpty', 'maxLyFull', 'maxSpeed', 'boostSpeed', 'stations' ])):
def name(self):
return self.dbname

class TradeDB(object):
"""
Expand Down Expand Up @@ -396,7 +392,7 @@ def load(self):
# the maximum "link" between any two stars.
longestJumper = max(ships.values(), key=lambda ship: ship.maxLyEmpty)
self.maxSystemLinkLy = longestJumper.maxLyEmpty + 0.01
if self.debug > 2: print("# Max ship jump distance: %s @ %f" % (longestJumper.name, self.maxSystemLinkLy))
if self.debug > 2: print("# Max ship jump distance: %s @ %f" % (longestJumper.name(), self.maxSystemLinkLy))

self.build_links(self.maxSystemLinkLy)

Expand All @@ -421,7 +417,9 @@ def _validate(self):
raise ValueError("System %s does not have a reciprocal link in %s's links" % (name, link.str()))

def getSystem(self, key):
""" Look up a System object by it's name. """
"""
Look up a System object by it's name.
"""
if isinstance(key, System):
return name
if isinstance(key, Station):
Expand All @@ -430,7 +428,9 @@ def getSystem(self, key):
return TradeDB.list_search("System", name, self.systems.values(), key=lambda system: system.name)

def getStation(self, name):
""" Look up a Station object by it's name or system. """
"""
Look up a Station object by it's name or system.
"""
if isinstance(name, Station):
return name
if isinstance(name, System):
Expand All @@ -441,45 +441,43 @@ def getStation(self, name):

stationID, station, systemID, system = None, None, None, None
try:
system = TradeDB.list_search("System", name, self.systems.values(), key=lambda system: system.name)
system = TradeDB.list_search("System", name, self.systemByID.values(), key=lambda system: system.dbname)
except LookupError:
pass
try:
stationName = TradeDB.list_search("Station", name, self.stationIDs.keys())
stationID = self.stationIDs[stationName]
station = self.stations[stationID]
station = TradeDB.list_search("Station", name, self.stationByID.values(), key=lambda station: station.dbname)
except LookupError:
pass
# If neither matched, we have a lookup error.
if not (stationID or systemID):
if not (station or system):
raise LookupError("'%s' did not match any station or system." % (name))

# If we matched both a station and a system, make sure they resovle to the
# the same station otherwise we have an ambiguity. Some stations have the
# same name as their star system (Aulin/Aulin Enterprise)
if systemID and stationID and system != station.system:
raise AmbiguityError('Station', name, system.str(), station.str())
if system and station and system != station.system:
raise AmbiguityError('Station', name, system.name(), station.name())

if stationID:
return self.stations[stationID]
if station:
return station

# If we only matched a system name, ensure that it's a single station system
# otherwise they need to specify a station name.
system = self.systems[systemID]
if len(system.stations) != 1:
raise ValueError("System '%s' has %d stations, please specify a station instead." % (name, len(system.stations)))
return system.stations[0]

def getShip(self, name):
""" Look up a ship by name """
return TradeDB.list_search("Ship", name, self.ships, key=lambda item: item.name)
"""
Look up a ship by name
"""
return TradeDB.list_search("Ship", name, self.shipByID.values(), key=lambda ship: ship.dbname)

def getTrade(self, src, dst, item):
""" Returns a Trade object describing purchase of item from src for sale at dst. """
srcStn = self.getStation(src)
dstStn = self.getStation(dst)
trades = srcStn.trades[dstStn.ID]
return trades[item]
return srcStn.tradingWith[dstStn]

@staticmethod
def getDistanceSq(lhsX, lhsY, lhsZ, rhsX, rhsY, rhsZ):
Expand Down

0 comments on commit 42413d1

Please sign in to comment.