diff --git a/core/src/main/java/ru/trader/analysis/RouteEntry.java b/core/src/main/java/ru/trader/analysis/RouteEntry.java index 08ce73d..0feef19 100644 --- a/core/src/main/java/ru/trader/analysis/RouteEntry.java +++ b/core/src/main/java/ru/trader/analysis/RouteEntry.java @@ -10,11 +10,10 @@ import java.util.List; public class RouteEntry { private final Vendor vendor; private final double fuel; - private final double score; private final List orders; private boolean land; private boolean refill; - + private double score; public RouteEntry(Vendor vendor, boolean refill, double fuel, double score) { orders = new ArrayList<>(); @@ -48,6 +47,10 @@ public class RouteEntry { return score; } + void setScore(double score) { + this.score = score; + } + public void add(Order order){ orders.add(order); } diff --git a/core/src/main/java/ru/trader/analysis/RouteFiller.java b/core/src/main/java/ru/trader/analysis/RouteFiller.java index 392b840..462d793 100644 --- a/core/src/main/java/ru/trader/analysis/RouteFiller.java +++ b/core/src/main/java/ru/trader/analysis/RouteFiller.java @@ -178,6 +178,8 @@ public class RouteFiller { if (best != TRANSIT) entry.addAll(best.bestOrders); } + int jumps = i==0 || entries.get(i-1).getVendor().getPlace().equals(entry.getVendor().getPlace())? 0 : 1; + entry.setScore(i==0 ? 0 : scorer.getScore(entry, jumps)); } } diff --git a/core/src/main/java/ru/trader/analysis/RouteSearcher.java b/core/src/main/java/ru/trader/analysis/RouteSearcher.java index a85a027..fabefb2 100644 --- a/core/src/main/java/ru/trader/analysis/RouteSearcher.java +++ b/core/src/main/java/ru/trader/analysis/RouteSearcher.java @@ -63,18 +63,10 @@ public class RouteSearcher { private Route toRoute(List> edges){ List entries = new ArrayList<>(edges.size()+1); Vendor buyer = null; + VendorsGraph.VendorsEdge edge = null; for (int i = 0; i < edges.size(); i++) { - VendorsGraph.VendorsEdge edge = (VendorsGraph.VendorsEdge) edges.get(i); - if (i==0){ - RouteEntry entry = new RouteEntry(edge.getSource().getEntry(), false, 0, 0); - List orders = edge.getOrders(); - if (!orders.isEmpty()){ - buyer = orders.get(0).getBuyer(); - } - entry.addAll(orders); - entries.add(entry); - } - Vendor vendor = edge.getTarget().getEntry(); + edge = (VendorsGraph.VendorsEdge) edges.get(i); + Vendor vendor = edge.getSource().getEntry(); RouteEntry entry = new RouteEntry(vendor, edge.isRefill(), edge.getFuel(), edge.getWeight()); if (buyer != null && vendor.equals(buyer)){ entry.setLand(true); @@ -84,7 +76,7 @@ public class RouteSearcher { buyer = orders.get(0).getBuyer(); if (vendor instanceof TransitVendor){ Vendor seller = orders.get(0).getSell().getVendor(); - for (int j = i-1; j <= 0; j--) { + for (int j = i-1; j >= 0; j--) { RouteEntry sEntry = entries.get(j); if (sEntry.is(seller)){ sEntry.addAll(orders); @@ -97,6 +89,11 @@ public class RouteSearcher { } entries.add(entry); } + if (edge != null) { + RouteEntry entry = new RouteEntry(edge.getTarget().getEntry(), false, 0, 0); + if (buyer != null) entry.setLand(true); + entries.add(entry); + } return new Route(entries); } diff --git a/core/src/main/java/ru/trader/analysis/Scorer.java b/core/src/main/java/ru/trader/analysis/Scorer.java index c05d6bd..8e64dec 100644 --- a/core/src/main/java/ru/trader/analysis/Scorer.java +++ b/core/src/main/java/ru/trader/analysis/Scorer.java @@ -18,6 +18,8 @@ public class Scorer { private final Profile profile; private final double avgProfit; + private final double minProfit; + private final double maxProfit; private final double maxScore; private final double avgDistance; @@ -28,9 +30,12 @@ public class Scorer { buyOffers = new HashMap<>(100, 0.9f); market.getItems().parallelStream().forEach(this::fillOffers); DoubleSummaryStatistics statProfit = computeProfit(); - avgProfit = statProfit.getAverage(); + minProfit = statProfit.getMin() / profile.getShip().getCargo(); + avgProfit = statProfit.getAverage() / profile.getShip().getCargo(); + maxProfit = statProfit.getMax() / profile.getShip().getCargo(); + avgDistance = computeAvgDistance(); - maxScore = getScore(0, statProfit.getMax()*2, 0,0,0); + maxScore = getScore(1, statProfit.getMax(), 0, 1, 0); } public Profile getProfile() { @@ -60,30 +65,64 @@ public class Scorer { } public double getAvgProfit() { - return avgProfit; + return avgProfit * profile.getShip().getCargo(); } public double getMaxScore() { return maxScore; } + public double getAvgDistance() { + return avgDistance; + } + public double getFuel(double distance){ return profile.getShip().getFuelCost(distance); } + public double getScore(RouteEntry entry, int jumps) { + int lands = entry.isLand() ? 1 : 0; + return getScore(entry.getVendor(), entry.getProfit(), jumps, lands, entry.getFuel()); + } + public double getScore(Vendor vendor, double profit, int jumps, int lands, double fuel) { return getScore(vendor.getDistance(), profit, jumps, lands, fuel); } + public double getTransitScore(double fuel){ + LOG.trace("Compute transit score fuel={}", fuel); + double profit = maxProfit; + profit -= profile.getFuelPrice() * fuel / profile.getShip().getCargo(); + double score = 1; + score -= profile.getLandMult(); + score -= profile.getJumpMult(); + score = score * profit; + if (avgDistance > 0) { + score -= - avgProfit * profile.getDistanceMult(); + } + LOG.trace("score={}", score); + return score; + } + public double getScore(double distance, double profit, int jumps, int lands, double fuel){ LOG.trace("Compute score distance={}, profit={}, jumps={}, lands={}, fuel={}", distance, profit, jumps, lands, fuel); - double score = profit; - if (avgDistance > 0 && profit > 0) { - score -= profile.getDistanceMult() * getAvgProfit() * (distance - avgDistance) / avgDistance; + profit -= profile.getFuelPrice() * fuel; + profit = profit / profile.getShip().getCargo(); + double score = 1; + if (profit > 0) { + score -= profile.getJumpMult() * (jumps - 1); + score -= profile.getLandMult() * lands; + if (score == 0) { + score = profit; + } else { + score = score * profit; + } + if (avgDistance > 0) { + score -= avgProfit * profile.getDistanceMult() * (distance - avgDistance) / avgDistance; + } + } else { + score = profit; } - score -= profile.getLandMult() * lands * getAvgProfit(); - score -= profile.getFuelPrice() * fuel; - score -= profile.getJumpMult() * jumps * getAvgProfit(); LOG.trace("score={}", score); return score; } diff --git a/core/src/main/java/ru/trader/analysis/VendorsGraph.java b/core/src/main/java/ru/trader/analysis/VendorsGraph.java index eeafb4a..5eb2eca 100644 --- a/core/src/main/java/ru/trader/analysis/VendorsGraph.java +++ b/core/src/main/java/ru/trader/analysis/VendorsGraph.java @@ -42,9 +42,13 @@ public class VendorsGraph extends ConnectibleGraph { } @Override - protected double onConnect(Vendor entry) { - double nextlimit = super.onConnect(entry); - if (entry instanceof TransitVendor && vertex.getEntry().getPlace().equals(entry.getPlace())) nextlimit = -1; + protected double onConnect(Vendor buyer) { + double nextlimit = super.onConnect(buyer); + Vendor seller = vertex.getEntry(); + if (nextlimit > 0){ + if (buyer instanceof TransitVendor && seller.getPlace().equals(buyer.getPlace())) nextlimit = -1; + if (seller instanceof TransitVendor && seller.getPlace().equals(buyer.getPlace())) nextlimit = -1; + } return nextlimit; } @@ -83,9 +87,15 @@ public class VendorsGraph extends ConnectibleGraph { @Override protected double computeWeight() { int jumps = source.getEntry().getPlace().equals(target.getEntry().getPlace())? 0 : 1; - int lands = refill && orders.isEmpty() || isTarget ? 1 : 0; - double score = scorer.getScore(target.getEntry(), getProfit(), jumps, lands, fuel); - return scorer.getMaxScore() - score; + int lands = refill || !orders.isEmpty() || isTarget ? 1 : 0; + boolean transit = lands == 0 && source.getEntry() instanceof TransitVendor || target.getEntry() instanceof TransitVendor; + double profit = getProfit(); + double score = transit ? scorer.getTransitScore(fuel) : + scorer.getScore(target.getEntry(), profit, jumps, lands, fuel); + score = scorer.getMaxScore() - score; + if (score < 0) + score = 0; + return score; } } @@ -101,7 +111,7 @@ public class VendorsGraph extends ConnectibleGraph { } @Override - protected CostTraversalEntry travers(CostTraversalEntry entry, List> head, Edge edge, Vendor target) { + protected CostTraversalEntry travers(final CostTraversalEntry entry, final List> head, final Edge edge, final Vendor target) { VendorsTraversalEntry ve = (VendorsTraversalEntry)entry; double balance = ve.balance; Vendor buyer = edge.getTarget().getEntry(); @@ -123,7 +133,7 @@ public class VendorsGraph extends ConnectibleGraph { ConnectibleEdge cedge = (ConnectibleEdge) ce.getEdge(); VendorsEdge addingEdge = new VendorsEdge(cedge.getSource(), cedge.getTarget(), cedge.isRefill(), cedge.getFuel(), target.equals(buyer)); addingEdge.setOrders(orders); - return new VendorsTraversalEntry(ce, head, addingEdge, balance+addingEdge.getProfit()); + return new VendorsTraversalEntry(head, addingEdge, entry.getWeight(), ce.getFuel(), balance+addingEdge.getProfit()); } protected class VendorsTraversalEntry extends CCostTraversalEntry { @@ -134,8 +144,8 @@ public class VendorsGraph extends ConnectibleGraph { this.balance = balance; } - protected VendorsTraversalEntry(CCostTraversalEntry entry, List> head, Edge edge, double balance) { - super(head, edge, entry.getWeight(), entry.getFuel()); + protected VendorsTraversalEntry(List> head, Edge edge, double cost, double fuel, double balance) { + super(head, edge, cost, fuel); this.balance = balance; } diff --git a/core/src/main/java/ru/trader/analysis/graph/AbstractGraph.java b/core/src/main/java/ru/trader/analysis/graph/AbstractGraph.java index 4afd9da..e576093 100644 --- a/core/src/main/java/ru/trader/analysis/graph/AbstractGraph.java +++ b/core/src/main/java/ru/trader/analysis/graph/AbstractGraph.java @@ -82,6 +82,11 @@ public abstract class AbstractGraph implements Graph { return minJumps; } + @Override + public int getMinLevel() { + return root.getLevel() - minJumps; + } + protected abstract class GraphBuilder extends RecursiveAction { protected final Vertex vertex; protected final Collection set; diff --git a/core/src/main/java/ru/trader/analysis/graph/Crawler.java b/core/src/main/java/ru/trader/analysis/graph/Crawler.java index a9333fe..6cff590 100644 --- a/core/src/main/java/ru/trader/analysis/graph/Crawler.java +++ b/core/src/main/java/ru/trader/analysis/graph/Crawler.java @@ -42,7 +42,7 @@ public class Crawler { if (count > 1) { Vertex s = graph.getRoot(); s.sortEdges(); - found = bfs(start(s), target, 0, count); + found = bfs(start(s), target, graph.getMinLevel(), count); } else { found = dfs(start(graph.getRoot()), target, t.getLevel() + 1, count); } @@ -58,7 +58,7 @@ public class Crawler { Vertex t = graph.getVertex(target); int found = 0; if (t != null) { - found = ucs(start(graph.getRoot()), target, 0, count); + found = ucs(start(graph.getRoot()), target, graph.getMinLevel(), count); } LOG.debug("Found {} paths", found); } @@ -153,7 +153,7 @@ public class Crawler { found++; if (found >= count) break; } - if (edge.getTarget().isSingle() || deep >= source.getLevel()){ + if (edge.getTarget().isSingle()){ continue; } head = getCopyList(entry.head, edge); @@ -166,7 +166,7 @@ public class Crawler { //put only 2 entry for iterate while (iterator.hasNext()){ edge = iterator.next(); - if (deep < source.getLevel() && !edge.getTarget().isSingle() || edge.isConnect(target)) { + if (deep <= source.getLevel() && !edge.getTarget().isSingle() || edge.isConnect(target)) { LOG.trace("Add edge {} to queue", edge); queue.add(travers(entry, head, edge, target)); } diff --git a/core/src/main/java/ru/trader/analysis/graph/Graph.java b/core/src/main/java/ru/trader/analysis/graph/Graph.java index c069dab..aa4dec2 100644 --- a/core/src/main/java/ru/trader/analysis/graph/Graph.java +++ b/core/src/main/java/ru/trader/analysis/graph/Graph.java @@ -8,4 +8,6 @@ public interface Graph { Vertex getRoot(); int getMinJumps(); + + int getMinLevel(); } diff --git a/core/src/main/java/ru/trader/core/Profile.java b/core/src/main/java/ru/trader/core/Profile.java index 9bb5af3..b742e3a 100644 --- a/core/src/main/java/ru/trader/core/Profile.java +++ b/core/src/main/java/ru/trader/core/Profile.java @@ -18,10 +18,10 @@ public class Profile { this.ship = ship; refill = true; scoreOrdersCount = 5; - distanceMult = 1; - landMult = 1; - fuelPrice = 1; - jumpMult = 0.01; + distanceMult = 0.08; + landMult = 0.95; + fuelPrice = 100; + jumpMult = 0.008; } public double getBalance() { diff --git a/core/src/main/java/ru/trader/core/TransitVendor.java b/core/src/main/java/ru/trader/core/TransitVendor.java index 05e1580..6394928 100644 --- a/core/src/main/java/ru/trader/core/TransitVendor.java +++ b/core/src/main/java/ru/trader/core/TransitVendor.java @@ -93,6 +93,11 @@ public class TransitVendor implements Vendor { return d == 0 ? 0 : d > 0 ? 1 : -1; } + @Override + public boolean canRefill() { + return false; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -108,6 +113,6 @@ public class TransitVendor implements Vendor { @Override public String toString() { - return "Transit - "+place; + return "Transit "+place; } } diff --git a/core/src/test/java/ru/trader/analysis/RouteSearcherTest.java b/core/src/test/java/ru/trader/analysis/RouteSearcherTest.java index 880e177..f8d2b8b 100644 --- a/core/src/test/java/ru/trader/analysis/RouteSearcherTest.java +++ b/core/src/test/java/ru/trader/analysis/RouteSearcherTest.java @@ -20,6 +20,8 @@ public class RouteSearcherTest extends Assert{ private Place ithaca; private Place lhs3262; + private Place morgor; + private Place lhs3006; @Before public void setUp() throws Exception { @@ -27,6 +29,8 @@ public class RouteSearcherTest extends Assert{ world = Store.loadFromFile(is); ithaca = world.get("Ithaca"); lhs3262 = world.get("LHS 3262"); + morgor = world.get("Morgor"); + lhs3006 = world.get("LHS 3006"); MarketFilter filter = new MarketFilter(); fWorld = new FilteredMarket(world, filter); @@ -38,17 +42,30 @@ public class RouteSearcherTest extends Assert{ // Ithaca (Palladium to LHS 3262) -> Morgor -> LHS 3006 -> LHS 3262 (Consumer Technology to Ithaca) -> LHS 3006 -> Morgor -> Ithaca // Profit: 981200, avg: 490600, distance: 67.5, lands: 2 Vendor ithaca_st = ithaca.get().iterator().next(); + Vendor lhs3262_st = lhs3262.get().iterator().next(); + Vendor morgor_st = morgor.get().iterator().next(); + Vendor lhs3006_st = lhs3006.get().iterator().next(); Ship ship = new Ship(); - ship.setCargo(440); ship.setTank(16); + ship.setCargo(440); ship.setTank(15); ship.setEngine(5, 'A'); ship.setMass(466); Profile profile = new Profile(ship); profile.setBalance(6000000); profile.setJumps(6); - profile.setRoutesCount(10); + profile.setRoutesCount(100); Scorer scorer = new Scorer(fWorld, profile); LOG.info("Start test routes"); RouteSearcher searcher = new RouteSearcher(scorer); + Route route = new Route(new RouteEntry(ithaca_st, false, 3.3789702637348586d, 0)); + route.add(new RouteEntry(morgor_st, false, 4.137765020523591d, 0)); + route.add(new RouteEntry(lhs3006_st, false, 4.0674474942172765d, 0)); + route.add(new RouteEntry(lhs3262_st, true, 4.149937831634785d, 0)); + route.add(new RouteEntry(lhs3006_st, false, 4.1292528548103d, 0)); + route.add(new RouteEntry(morgor_st, false, 3.3050364899848566, 0)); + route.add(new RouteEntry(ithaca_st, false, 3.3483447506734136, 0)); + RouteFiller filler = new RouteFiller(scorer); + filler.fill(route); + List apaths = searcher.getRoutes(ithaca_st, ithaca_st, fWorld.getMarkets(true).collect(Collectors.toList())); Route actual = apaths.stream().findFirst().get(); //assertTrue("Routes is different",expect.isRoute(actual)); diff --git a/core/src/test/java/ru/trader/analysis/ScorerTest.java b/core/src/test/java/ru/trader/analysis/ScorerTest.java index 4562106..4d400d6 100644 --- a/core/src/test/java/ru/trader/analysis/ScorerTest.java +++ b/core/src/test/java/ru/trader/analysis/ScorerTest.java @@ -108,6 +108,87 @@ public class ScorerTest extends Assert { } + @Test + public void testScore2() throws Exception { + Ship ship = new Ship(); + ship.setCargo(100); + Profile profile = new Profile(ship); + LOG.info("Start score test, balance 10000000"); + profile.setBalance(1000000); + Scorer scorer = new Scorer(fWorld, profile); + + double transitScore = scorer.getTransitScore(4); + double transitScore2 = scorer.getTransitScore(6); + double transitScore3 = scorer.getTransitScore(2); + + assertTrue(transitScore > transitScore2); + assertTrue(transitScore3 > transitScore); + + double score = scorer.getScore(scorer.getAvgDistance(), 0, 1, 1, 4); + double score1 = scorer.getScore(scorer.getAvgDistance(), scorer.getAvgProfit()/2, 1, 1, 4); + double score2 = scorer.getScore(scorer.getAvgDistance(), scorer.getAvgProfit()*10, 1, 1, 4); + double score3 = scorer.getScore(scorer.getAvgDistance(), scorer.getAvgProfit(), 1, 1, 4); + + assertTrue(score < score1); + assertTrue(score1 < score3); + assertTrue(score3 < score2); + assertTrue(score3 < score2); + + assertTrue(Math.abs(score2/score1) >= 20); + assertTrue(Math.abs(score3/score1) >= 2); + assertTrue(Math.abs(score2/score3) >= 10); + + + score1 = scorer.getScore(scorer.getAvgDistance(), scorer.getAvgProfit(), 0, 1, 4); + score2 = scorer.getScore(scorer.getAvgDistance(), scorer.getAvgProfit(), 1, 1, 4); + score3 = scorer.getScore(scorer.getAvgDistance(), scorer.getAvgProfit(), 2, 1, 4); + assertTrue(score1 > score2); + assertTrue(score2 > score3); + + score1 = scorer.getScore(scorer.getAvgDistance(), scorer.getAvgProfit(), 1, 0, 4); + score2 = scorer.getScore(scorer.getAvgDistance(), scorer.getAvgProfit(), 1, 1, 4); + score3 = scorer.getScore(scorer.getAvgDistance(), scorer.getAvgProfit(), 1, 2, 4); + assertTrue(score1 > score2); + assertTrue(score2 > score3); + + score1 = scorer.getScore(scorer.getAvgDistance(), scorer.getAvgProfit(), 1, 1, 3); + score2 = scorer.getScore(scorer.getAvgDistance(), scorer.getAvgProfit(), 1, 1, 4); + score3 = scorer.getScore(scorer.getAvgDistance(), scorer.getAvgProfit(), 1, 1, 5); + assertTrue(score1 > score2); + assertTrue(score2 > score3); + + score = scorer.getScore(0, scorer.getAvgProfit(), 1, 1, 4); + score1 = scorer.getScore(scorer.getAvgDistance()/2, scorer.getAvgProfit(), 1, 1, 4); + score2 = scorer.getScore(scorer.getAvgDistance(), scorer.getAvgProfit(), 1, 1, 4); + score3 = scorer.getScore(scorer.getAvgDistance()*2, scorer.getAvgProfit(), 1, 1, 4); + assertTrue(score > score1); + assertTrue(score1 > score2); + assertTrue(score2 > score3); + + score1 = scorer.getScore(scorer.getAvgDistance()/2, scorer.getAvgProfit(), 1, 1, 4); + score2 = scorer.getScore(scorer.getAvgDistance()*2, scorer.getAvgProfit()*2, 1, 1, 4); + score3 = scorer.getScore(scorer.getAvgDistance()*2, scorer.getAvgProfit()*4, 1, 1, 4); + assertTrue(score1 > score2); + assertTrue(score3 > score1); + + score1 = scorer.getScore(scorer.getAvgDistance(), scorer.getAvgProfit()/2, 0, 1, 4); + score2 = scorer.getScore(scorer.getAvgDistance(), scorer.getAvgProfit()*2, 1, 1, 4); + score3 = scorer.getScore(scorer.getAvgDistance(), scorer.getAvgProfit()*4, 0, 1, 4); + assertTrue(score2 > score1); + assertTrue(score3 > score2); + + transitScore = scorer.getTransitScore(4); + score = scorer.getScore(scorer.getAvgDistance(), scorer.getAvgProfit(), 1, 1, 4); + score1 = scorer.getScore(scorer.getAvgDistance(), scorer.getAvgProfit()/2, 1, 1, 4); + score2 = scorer.getScore(scorer.getAvgDistance(), scorer.getAvgProfit()*10, 1, 1, 4); + score3 = scorer.getScore(scorer.getAvgDistance(), 0, 1, 1, 4); + assertTrue(transitScore > score); + assertTrue(transitScore > score1); + assertTrue(transitScore < score2); + assertTrue(transitScore > score3); + + } + @After public void tearDown() throws Exception { world = null; diff --git a/core/src/test/resources/log4j.properties b/core/src/test/resources/log4j.properties index e94868c..96fe20c 100644 --- a/core/src/test/resources/log4j.properties +++ b/core/src/test/resources/log4j.properties @@ -4,4 +4,4 @@ log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%p: %d{dd.MM.yyyy HH:mm:ss} (%F:%L) - %m%n -log4j.logger.ru.trader.analysis.graph = TRACE +#log4j.logger.ru.trader.analysis.graph = TRACE