diff --git a/PaySim.properties b/PaySim.properties index 73b50ba..04ce1fc 100644 --- a/PaySim.properties +++ b/PaySim.properties @@ -1,8 +1,8 @@ #PaySim parameters seed=time nbSteps=720 -multiplier=0.05 -nbClients=600000 +multiplier=1 +nbClients=20000 nbFraudsters=1000 nbMerchants=34749 nbBanks=5 @@ -14,6 +14,7 @@ clientsProfiles=./paramFiles/clientsProfiles.csv initialBalancesDistribution=./paramFiles/initialBalancesDistribution.csv overdraftLimits=./paramFiles/overdraftLimits.csv maxOccurrencesPerClient=./paramFiles/maxOccurrencesPerClient.csv +typologiesFolder=./paramFiles/typologies/ outputPath=./outputs/ saveToDB=0 dbUrl=jdbc:mysql://localhost:3306/paysim diff --git a/paramFiles/overdraftLimits.csv b/paramFiles/overdraftLimits.csv index 1f2f1cb..d574c8a 100644 --- a/paramFiles/overdraftLimits.csv +++ b/paramFiles/overdraftLimits.csv @@ -3,4 +3,4 @@ lowerbound,higherbound,overdraftLimit 0,50000,-25000 50000,100000,-75000 100000,200000,-150000 -200000,,200000 +200000,,-200000 diff --git a/paramFiles/typologies/DrugNetworkOne.graphml b/paramFiles/typologies/DrugNetworkOne.graphml new file mode 100644 index 0000000..9d20ebd --- /dev/null +++ b/paramFiles/typologies/DrugNetworkOne.graphml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + 1000 + + + 60 + 100 + + + + TRANSFER + 10:0.25, 20:0.50, 50:0.20, 200:0.05 + + + CASH_OUT + 1000 + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..29d2b52 --- /dev/null +++ b/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + org.paysim + paysim + 2.0-SNAPSHOT + + + + org.apache.maven.plugins + maven-compiler-plugin + + 8 + 8 + + + + + + + jitpack.io + https://jitpack.io + + + + + com.github.eclab + mason + 0663315 + + + + + org.apache.tinkerpop + tinkergraph-gremlin + 3.3.4 + + + + + org.slf4j + slf4j-simple + 1.7.25 + + + \ No newline at end of file diff --git a/src/paysim/PaySim.java b/src/main/java/org/paysim/paysim/PaySim.java similarity index 87% rename from src/paysim/PaySim.java rename to src/main/java/org/paysim/paysim/PaySim.java index 745e032..968c2c3 100644 --- a/src/paysim/PaySim.java +++ b/src/main/java/org/paysim/paysim/PaySim.java @@ -1,4 +1,4 @@ -package paysim; +package org.paysim.paysim; import java.io.File; import java.text.DateFormat; @@ -7,21 +7,22 @@ import sim.engine.SimState; -import paysim.parameters.*; +import org.paysim.paysim.parameters.*; -import paysim.actors.Bank; -import paysim.actors.Client; -import paysim.actors.Fraudster; -import paysim.actors.Merchant; +import org.paysim.paysim.actors.Bank; +import org.paysim.paysim.actors.Client; +import org.paysim.paysim.actors.Fraudster; +import org.paysim.paysim.actors.Merchant; +import org.paysim.paysim.actors.networkdrugs.NetworkDrug; -import paysim.base.Transaction; -import paysim.base.ClientActionProfile; -import paysim.base.StepActionProfile; +import org.paysim.paysim.base.Transaction; +import org.paysim.paysim.base.ClientActionProfile; +import org.paysim.paysim.base.StepActionProfile; -import paysim.output.Output; +import org.paysim.paysim.output.Output; public class PaySim extends SimState { - public static final double PAYSIM_VERSION = 1.0; + public static final double PAYSIM_VERSION = 2.0; private static final String[] DEFAULT_ARGS = new String[]{"", "-file", "PaySim.properties", "5"}; public final String simulationName; @@ -141,22 +142,20 @@ private void initActors() { //Add the clients System.out.println("NbClients: " + (int) (Parameters.nbClients * Parameters.multiplier)); for (int i = 0; i < Parameters.nbClients * Parameters.multiplier; i++) { - Client c = new Client(generateId(), - pickRandomBank(), - pickNextClientProfile(), - BalancesClients.pickNextBalance(random), - random, - Parameters.stepsProfiles.getTotalTargetCount()); + Client c = new Client(this); clients.add(c); } - //Schedule clients to act at each step of the simulation + NetworkDrug.createNetwork(this, Parameters.typologiesFolder + TypologiesFiles.drugNetworkOne); + + // Do not write code under this part otherwise clients will not be used in simulation + // Schedule clients to act at each step of the simulation for (Client c : clients) { schedule.scheduleRepeating(c); } } - private Map pickNextClientProfile() { + public Map pickNextClientProfile() { Map profile = new HashMap<>(); for (String action : ActionTypes.getActions()) { ClientActionProfile clientActionProfile = Parameters.clientsProfiles.pickNextActionProfile(action); @@ -218,7 +217,7 @@ public Client pickRandomClient(String nameOrig) { Client clientDest = null; String nameDest = nameOrig; - while (nameOrig.equals(nameDest)){ + while (nameOrig.equals(nameDest)) { clientDest = clients.get(random.nextInt(clients.size())); nameDest = clientDest.getName(); } @@ -241,15 +240,19 @@ public ArrayList getClients() { return clients; } + public void addClient(Client c) { + clients.add(c); + } + public int getStepTargetCount() { return Parameters.stepsProfiles.getTargetCount(currentStep); } - public Map getStepProbabilities(){ + public Map getStepProbabilities() { return Parameters.stepsProfiles.getProbabilitiesPerStep(currentStep); } - public StepActionProfile getStepAction(String action){ + public StepActionProfile getStepAction(String action) { return Parameters.stepsProfiles.getActionForStep(currentStep, action); } } \ No newline at end of file diff --git a/src/paysim/actors/Bank.java b/src/main/java/org/paysim/paysim/actors/Bank.java similarity index 83% rename from src/paysim/actors/Bank.java rename to src/main/java/org/paysim/paysim/actors/Bank.java index 893c467..9635611 100644 --- a/src/paysim/actors/Bank.java +++ b/src/main/java/org/paysim/paysim/actors/Bank.java @@ -1,4 +1,4 @@ -package paysim.actors; +package org.paysim.paysim.actors; public class Bank extends SuperActor { private static final String BANK_IDENTIFIER = "B"; diff --git a/src/paysim/actors/Client.java b/src/main/java/org/paysim/paysim/actors/Client.java similarity index 70% rename from src/paysim/actors/Client.java rename to src/main/java/org/paysim/paysim/actors/Client.java index e9ceb70..bb433ff 100644 --- a/src/paysim/actors/Client.java +++ b/src/main/java/org/paysim/paysim/actors/Client.java @@ -1,22 +1,27 @@ -package paysim.actors; +package org.paysim.paysim.actors; +import java.util.Arrays; +import java.util.HashMap; import java.util.Map; import static java.lang.Math.max; import ec.util.MersenneTwisterFast; -import paysim.parameters.ActionTypes; -import paysim.parameters.BalancesClients; import sim.engine.SimState; import sim.engine.Steppable; import sim.util.distribution.Binomial; -import paysim.PaySim; -import paysim.base.ClientActionProfile; -import paysim.base.ClientProfile; -import paysim.base.StepActionProfile; -import paysim.base.Transaction; -import paysim.parameters.Parameters; -import paysim.utils.RandomCollection; +import org.paysim.paysim.PaySim; + +import org.paysim.paysim.base.ClientActionProfile; +import org.paysim.paysim.base.ClientProfile; +import org.paysim.paysim.base.StepActionProfile; +import org.paysim.paysim.base.Transaction; + +import org.paysim.paysim.parameters.ActionTypes; +import org.paysim.paysim.parameters.Parameters; +import org.paysim.paysim.parameters.BalancesClients; + +import org.paysim.paysim.utils.RandomCollection; public class Client extends SuperActor implements Steppable { @@ -29,20 +34,22 @@ public class Client extends SuperActor implements Steppable { private double clientWeight; private double balanceMax = 0; private int countTransferTransactions = 0; + private double expectedAvgTransaction = 0; + private double initialBalance; Client(String name, Bank bank) { super(CLIENT_IDENTIFIER + name); this.bank = bank; } - public Client(String name, Bank bank, Map profile, double initBalance, - MersenneTwisterFast random, int totalTargetCount) { - super(CLIENT_IDENTIFIER + name); - this.bank = bank; - this.clientProfile = new ClientProfile(profile, random); - this.clientWeight = ((double) clientProfile.getClientTargetCount()) / totalTargetCount; - this.balance = initBalance; - this.overdraftLimit = pickOverdraftLimit(random); + public Client(PaySim paySim) { + super(CLIENT_IDENTIFIER + paySim.generateId()); + this.bank = paySim.pickRandomBank(); + this.clientProfile = new ClientProfile(paySim.pickNextClientProfile(), paySim.random); + this.clientWeight = ((double) clientProfile.getClientTargetCount()) / Parameters.stepsProfiles.getTotalTargetCount(); + this.initialBalance = BalancesClients.pickNextBalance(paySim.random); + this.balance = initialBalance; + this.overdraftLimit = pickOverdraftLimit(paySim.random); } @Override @@ -74,19 +81,46 @@ private int pickCount(MersenneTwisterFast random, int targetStepCount) { private String pickAction(MersenneTwisterFast random, Map stepActionProb) { Map clientProbabilities = clientProfile.getActionProbability(); + Map rawProbabilities = new HashMap<>(); RandomCollection actionPicker = new RandomCollection<>(random); + // Pick the compromise between the Step distribution and the Client distribution for (Map.Entry clientEntry : clientProbabilities.entrySet()) { String action = clientEntry.getKey(); double clientProbability = clientEntry.getValue(); - double finalProbability; + double rawProbability; if (stepActionProb.containsKey(action)) { double stepProbability = stepActionProb.get(action); - finalProbability = (clientProbability + stepProbability) / 2; + rawProbability = (clientProbability + stepProbability) / 2; } else { - finalProbability = clientProbability; + rawProbability = clientProbability; + } + rawProbabilities.put(action, rawProbability); + } + + // Correct the distribution so the balance of the account do not diverge too much + double probInflow = 0; + for (Map.Entry rawEntry : rawProbabilities.entrySet()) { + String action = rawEntry.getKey(); + if (isInflow(action)) { + probInflow += rawEntry.getValue(); + } + } + double probOutflow = 1 - probInflow; + double newProbInflow = computeProbWithSpring(probInflow, probOutflow, balance); + double newProbOutflow = 1 - newProbInflow; + + for (Map.Entry rawEntry : rawProbabilities.entrySet()) { + String action = rawEntry.getKey(); + double rawProbability = rawEntry.getValue(); + double finalProbability; + + if (isInflow(action)) { + finalProbability = rawProbability * newProbInflow / probInflow; + } else { + finalProbability = rawProbability * newProbOutflow / probOutflow; } actionPicker.add(finalProbability, action); } @@ -94,6 +128,33 @@ private String pickAction(MersenneTwisterFast random, Map stepAc return actionPicker.next(); } + /** + * The Biased Bernoulli Walk we were doing can go far to the equilibrium of an account + * To avoid this we conceptually add a spring that would be attached to the equilibrium position of the account + */ + private double computeProbWithSpring(double probUp, double probDown, double currentBalance){ + double equilibrium = 40 * expectedAvgTransaction; // Could also be the initial balance in other models + double correctionStrength = 3 * Math.pow(10, -5); // In a physical model it would be 1 / 2 * kB * T + double characteristicLengthSpring = equilibrium; + double k = 1 / characteristicLengthSpring; + double springForce = k * (equilibrium - currentBalance); + double newProbUp = 0.5d * ( 1d + (expectedAvgTransaction * correctionStrength) * springForce + (probUp - probDown)); + + if (newProbUp > 1){ + newProbUp = 1; + } else if (newProbUp < 0){ + newProbUp = 0; + } + return newProbUp; + + } + + private boolean isInflow(String action){ + String[] inflowActions = {CASH_IN, DEPOSIT}; + return Arrays.stream(inflowActions) + .anyMatch(action::equals); + } + private double pickAmount(MersenneTwisterFast random, String action, StepActionProfile stepAmountProfile) { ClientActionProfile clientAmountProfile = clientProfile.getProfilePerAction(action); @@ -135,7 +196,7 @@ private void makeTransaction(PaySim state, int step, String action, double amoun double reducedAmount = amount; boolean lastTransferFailed = false; while (reducedAmount > Parameters.transferLimit && !lastTransferFailed) { - lastTransferFailed = handleTransfer(state, step, Parameters.transferLimit, clientTo); + lastTransferFailed = !handleTransfer(state, step, Parameters.transferLimit, clientTo); reducedAmount -= Parameters.transferLimit; } if (reducedAmount > 0 && !lastTransferFailed) { @@ -150,7 +211,7 @@ private void makeTransaction(PaySim state, int step, String action, double amoun } } - private void handleCashIn(PaySim paysim, int step, double amount) { + protected void handleCashIn(PaySim paysim, int step, double amount) { Merchant merchantTo = paysim.pickRandomMerchant(); String nameOrig = this.getName(); String nameDest = merchantTo.getName(); @@ -167,7 +228,7 @@ private void handleCashIn(PaySim paysim, int step, double amount) { paysim.getTransactions().add(t); } - private void handleCashOut(PaySim paysim, int step, double amount) { + protected void handleCashOut(PaySim paysim, int step, double amount) { Merchant merchantTo = paysim.pickRandomMerchant(); String nameOrig = this.getName(); String nameDest = merchantTo.getName(); @@ -187,7 +248,7 @@ private void handleCashOut(PaySim paysim, int step, double amount) { paysim.getTransactions().add(t); } - private void handleDebit(PaySim paysim, int step, double amount) { + protected void handleDebit(PaySim paysim, int step, double amount) { String nameOrig = this.getName(); String nameDest = this.bank.getName(); double oldBalanceOrig = this.getBalance(); @@ -205,7 +266,7 @@ private void handleDebit(PaySim paysim, int step, double amount) { paysim.getTransactions().add(t); } - private void handlePayment(PaySim paysim, int step, double amount) { + protected void handlePayment(PaySim paysim, int step, double amount) { Merchant merchantTo = paysim.pickRandomMerchant(); String nameOrig = this.getName(); @@ -228,17 +289,17 @@ private void handlePayment(PaySim paysim, int step, double amount) { paysim.getTransactions().add(t); } - boolean handleTransfer(PaySim paysim, int step, double amount, Client clientTo) { + protected boolean handleTransfer(PaySim paysim, int step, double amount, Client clientTo) { String nameOrig = this.getName(); String nameDest = clientTo.getName(); double oldBalanceOrig = this.getBalance(); double oldBalanceDest = clientTo.getBalance(); - boolean transferFailed; + boolean transferSuccessful; if (!isDetectedAsFraud(amount)) { boolean isUnauthorizedOverdraft = this.withdraw(amount); - transferFailed = isUnauthorizedOverdraft; - if (!isUnauthorizedOverdraft) { + transferSuccessful = !isUnauthorizedOverdraft; + if (transferSuccessful) { clientTo.deposit(amount); } @@ -252,7 +313,7 @@ boolean handleTransfer(PaySim paysim, int step, double amount, Client clientTo) t.setFraud(this.isFraud()); paysim.getTransactions().add(t); } else { // create the transaction but don't move any money as the transaction was detected as fraudulent - transferFailed = true; + transferSuccessful = false; double newBalanceOrig = this.getBalance(); double newBalanceDest = clientTo.getBalance(); @@ -263,10 +324,10 @@ boolean handleTransfer(PaySim paysim, int step, double amount, Client clientTo) t.setFraud(this.isFraud()); paysim.getTransactions().add(t); } - return transferFailed; + return transferSuccessful; } - private void handleDeposit(PaySim paysim, int step, double amount) { + protected void handleDeposit(PaySim paysim, int step, double amount) { String nameOrig = this.getName(); String nameDest = this.bank.getName(); double oldBalanceOrig = this.getBalance(); @@ -297,17 +358,17 @@ private boolean isDetectedAsFraud(double amount) { } private double pickOverdraftLimit(MersenneTwisterFast random){ - double averageTransaction = 0, stdTransaction = 0; + double stdTransaction = 0; for (String action: ActionTypes.getActions()){ double actionProbability = clientProfile.getActionProbability().get(action); ClientActionProfile actionProfile = clientProfile.getProfilePerAction(action); - averageTransaction += actionProfile.getAvgAmount() * actionProbability; + expectedAvgTransaction += actionProfile.getAvgAmount() * actionProbability; stdTransaction += Math.pow(actionProfile.getStdAmount() * actionProbability, 2); } stdTransaction = Math.sqrt(stdTransaction); - double randomizedMeanTransaction = random.nextGaussian() * stdTransaction + averageTransaction; + double randomizedMeanTransaction = random.nextGaussian() * stdTransaction + expectedAvgTransaction; return BalancesClients.getOverdraftLimit(randomizedMeanTransaction); } diff --git a/src/paysim/actors/Fraudster.java b/src/main/java/org/paysim/paysim/actors/Fraudster.java similarity index 82% rename from src/paysim/actors/Fraudster.java rename to src/main/java/org/paysim/paysim/actors/Fraudster.java index 36807ce..d4be018 100644 --- a/src/paysim/actors/Fraudster.java +++ b/src/main/java/org/paysim/paysim/actors/Fraudster.java @@ -1,13 +1,14 @@ -package paysim.actors; +package org.paysim.paysim.actors; import java.util.ArrayList; import sim.engine.SimState; import sim.engine.Steppable; -import paysim.PaySim; -import paysim.parameters.Parameters; -import paysim.output.Output; +import org.paysim.paysim.PaySim; +import org.paysim.paysim.parameters.Parameters; + +import org.paysim.paysim.output.Output; public class Fraudster extends SuperActor implements Steppable { private static final String FRAUDSTER_IDENTIFIER = "C"; @@ -34,17 +35,17 @@ public void step(SimState state) { Mule muleClient = new Mule(paysim.generateId(), paysim.pickRandomBank()); muleClient.setFraud(true); if (balance > Parameters.transferLimit) { - transferFailed = c.handleTransfer(paysim, step, Parameters.transferLimit, muleClient); + transferFailed = !c.handleTransfer(paysim, step, Parameters.transferLimit, muleClient); balance -= Parameters.transferLimit; } else { - transferFailed = c.handleTransfer(paysim, step, balance, muleClient); + transferFailed = !c.handleTransfer(paysim, step, balance, muleClient); balance = 0; } profit += muleClient.getBalance(); muleClient.fraudulentCashOut(paysim, step, muleClient.getBalance()); nbVictims++; - paysim.getClients().add(muleClient); + paysim.addClient(muleClient); if (transferFailed) break; } diff --git a/src/paysim/actors/Merchant.java b/src/main/java/org/paysim/paysim/actors/Merchant.java similarity index 84% rename from src/paysim/actors/Merchant.java rename to src/main/java/org/paysim/paysim/actors/Merchant.java index eefee4f..0ad2d5c 100644 --- a/src/paysim/actors/Merchant.java +++ b/src/main/java/org/paysim/paysim/actors/Merchant.java @@ -1,4 +1,4 @@ -package paysim.actors; +package org.paysim.paysim.actors; public class Merchant extends SuperActor { private static final String MERCHANT_IDENTIFIER = "M"; diff --git a/src/paysim/actors/Mule.java b/src/main/java/org/paysim/paysim/actors/Mule.java similarity index 89% rename from src/paysim/actors/Mule.java rename to src/main/java/org/paysim/paysim/actors/Mule.java index f3ad133..9debe2a 100644 --- a/src/paysim/actors/Mule.java +++ b/src/main/java/org/paysim/paysim/actors/Mule.java @@ -1,7 +1,7 @@ -package paysim.actors; +package org.paysim.paysim.actors; -import paysim.PaySim; -import paysim.base.Transaction; +import org.paysim.paysim.PaySim; +import org.paysim.paysim.base.Transaction; public class Mule extends Client { private static final String MULE_IDENTIFIER = "C"; diff --git a/src/paysim/actors/SuperActor.java b/src/main/java/org/paysim/paysim/actors/SuperActor.java similarity index 92% rename from src/paysim/actors/SuperActor.java rename to src/main/java/org/paysim/paysim/actors/SuperActor.java index dc62fe4..46e492e 100644 --- a/src/paysim/actors/SuperActor.java +++ b/src/main/java/org/paysim/paysim/actors/SuperActor.java @@ -1,4 +1,4 @@ -package paysim.actors; +package org.paysim.paysim.actors; class SuperActor { private final String name; @@ -34,7 +34,7 @@ void setFraud(boolean isFraud) { this.isFraud = isFraud; } - double getBalance() { + protected double getBalance() { return balance; } diff --git a/src/main/java/org/paysim/paysim/actors/networkdrugs/DrugConsumer.java b/src/main/java/org/paysim/paysim/actors/networkdrugs/DrugConsumer.java new file mode 100644 index 0000000..72b887b --- /dev/null +++ b/src/main/java/org/paysim/paysim/actors/networkdrugs/DrugConsumer.java @@ -0,0 +1,52 @@ +package org.paysim.paysim.actors.networkdrugs; + +import ec.util.MersenneTwisterFast; +import org.paysim.paysim.parameters.Parameters; +import sim.engine.SimState; + +import org.paysim.paysim.PaySim; +import org.paysim.paysim.actors.Client; +import org.paysim.paysim.utils.RandomCollection; + +public class DrugConsumer extends Client { + private DrugDealer dealer; + private RandomCollection probAmountProfile; + private double probabilityBuy; + + public DrugConsumer(PaySim paySim, DrugDealer dealer, double monthlySpending, RandomCollection probAmountProfile, double meanTr) { + super(paySim); + this.dealer = dealer; + this.probAmountProfile = probAmountProfile; + this.probabilityBuy = monthlySpending / meanTr / Parameters.nbSteps; + } + + @Override + public void step(SimState state) { + PaySim paySim = (PaySim) state; + int step = (int) paySim.schedule.getSteps(); + + super.step(state); + + if (wantsToBuyDrugs(paySim.random)) { + double amount = pickAmount(); + + handleTransferDealer(paySim, step, amount); + } + } + + private void handleTransferDealer(PaySim paySim, int step, double amount) { + boolean success = handleTransfer(paySim, step, amount, dealer); + + if (success) { + dealer.addMoneyFromDrug(amount); + } + } + + private boolean wantsToBuyDrugs(MersenneTwisterFast random) { + return random.nextBoolean(probabilityBuy); + } + + private double pickAmount() { + return probAmountProfile.next(); + } +} diff --git a/src/main/java/org/paysim/paysim/actors/networkdrugs/DrugDealer.java b/src/main/java/org/paysim/paysim/actors/networkdrugs/DrugDealer.java new file mode 100644 index 0000000..916df98 --- /dev/null +++ b/src/main/java/org/paysim/paysim/actors/networkdrugs/DrugDealer.java @@ -0,0 +1,45 @@ +package org.paysim.paysim.actors.networkdrugs; + +import sim.engine.SimState; + +import org.paysim.paysim.PaySim; +import org.paysim.paysim.actors.Client; + +public class DrugDealer extends Client { + private double thresholdForCashOut; + private double drugMoneyInAccount; + + public DrugDealer(PaySim paySim, double thresholdForCashOut) { + super(paySim); + this.thresholdForCashOut = thresholdForCashOut; + this.drugMoneyInAccount = 0; + } + + @Override + public void step(SimState state) { + PaySim paySim = (PaySim) state; + int step = (int) paySim.schedule.getSteps(); + + super.step(state); + + if (wantsToCashOutProfit()) { + double amount = pickAmountCashOutProfit(); + super.handleCashOut(paySim, step, amount); + drugMoneyInAccount -= amount; + } + } + + private boolean wantsToCashOutProfit(){ + //TODO: implement a randomized version + return drugMoneyInAccount > thresholdForCashOut; + } + + private double pickAmountCashOutProfit(){ + //TODO: implement a randomized version + return thresholdForCashOut; + } + + protected void addMoneyFromDrug(double amount){ + drugMoneyInAccount += amount; + } +} diff --git a/src/main/java/org/paysim/paysim/actors/networkdrugs/NetworkDrug.java b/src/main/java/org/paysim/paysim/actors/networkdrugs/NetworkDrug.java new file mode 100644 index 0000000..0b8c8f4 --- /dev/null +++ b/src/main/java/org/paysim/paysim/actors/networkdrugs/NetworkDrug.java @@ -0,0 +1,56 @@ +package org.paysim.paysim.actors.networkdrugs; + +import ec.util.MersenneTwisterFast; +import org.apache.tinkerpop.gremlin.structure.*; + +import org.paysim.paysim.PaySim; +import org.paysim.paysim.utils.GraphUtils; +import org.paysim.paysim.utils.RandomCollection; + +import java.util.Map; + +public class NetworkDrug { + public static void createNetwork(PaySim paySim, String drugNetworkFile) { + // Load graph file + Graph graph = GraphUtils.loadFromFile(drugNetworkFile); + + // Load dealer parameters + Vertex drugDealer = GraphUtils.getVertex(graph, "DrugDealer"); + double thresholdDealer = (double) GraphUtils.getProperty(drugDealer, "thresholdForCashOut"); + DrugDealer dealer = new DrugDealer(paySim, thresholdDealer); + paySim.addClient(dealer); + + // Load consumers parameters + Vertex drugConsumers = GraphUtils.getVertex(graph, "DrugConsumers"); + int nbConsumer = (int) GraphUtils.getProperty(drugConsumers, "count"); + double monthlySpending = (double) GraphUtils.getProperty(drugConsumers, "monthlySpending"); + + Edge buyDrugs = GraphUtils.getEdge(graph, "BuyDrugs"); + String probAmountProfileSerialized = (String) GraphUtils.getProperty(buyDrugs, "probAmountProfile"); + + Map mapProbAmountProfile = GraphUtils.unserializeMap(probAmountProfileSerialized); + double meanTr = computeMean(mapProbAmountProfile); + RandomCollection probAmountProfile = mapToRandomCollection(mapProbAmountProfile, paySim.random); + + for (int i = 0; i < nbConsumer; i++) { + paySim.addClient(new DrugConsumer(paySim, dealer, monthlySpending, probAmountProfile, meanTr)); + } + } + + private static double computeMean(Map map) { + double mean = 0; + for (Map.Entry entry : map.entrySet()) { + mean += entry.getKey() * entry.getValue(); + } + return mean; + } + + private static RandomCollection mapToRandomCollection(Map map, MersenneTwisterFast random){ + RandomCollection randomCollection = new RandomCollection<>(); + randomCollection.setRandom(random); + for (Map.Entry entry : map.entrySet()) { + randomCollection.add(entry.getValue(), entry.getKey()); + } + return randomCollection; + } +} diff --git a/src/paysim/base/ClientActionProfile.java b/src/main/java/org/paysim/paysim/base/ClientActionProfile.java similarity index 95% rename from src/paysim/base/ClientActionProfile.java rename to src/main/java/org/paysim/paysim/base/ClientActionProfile.java index 69f5114..9ce6c82 100644 --- a/src/paysim/base/ClientActionProfile.java +++ b/src/main/java/org/paysim/paysim/base/ClientActionProfile.java @@ -1,4 +1,4 @@ -package paysim.base; +package org.paysim.paysim.base; public class ClientActionProfile { private final String action; diff --git a/src/paysim/base/ClientProfile.java b/src/main/java/org/paysim/paysim/base/ClientProfile.java similarity index 96% rename from src/paysim/base/ClientProfile.java rename to src/main/java/org/paysim/paysim/base/ClientProfile.java index 2daadd1..5c9a571 100644 --- a/src/paysim/base/ClientProfile.java +++ b/src/main/java/org/paysim/paysim/base/ClientProfile.java @@ -1,4 +1,4 @@ -package paysim.base; +package org.paysim.paysim.base; import java.util.HashMap; import java.util.Map; @@ -6,7 +6,7 @@ import ec.util.MersenneTwisterFast; -import paysim.parameters.ActionTypes; +import org.paysim.paysim.parameters.ActionTypes; public class ClientProfile { private Map profile; diff --git a/src/paysim/base/StepActionProfile.java b/src/main/java/org/paysim/paysim/base/StepActionProfile.java similarity index 95% rename from src/paysim/base/StepActionProfile.java rename to src/main/java/org/paysim/paysim/base/StepActionProfile.java index 9140ea2..4bd9c4c 100644 --- a/src/paysim/base/StepActionProfile.java +++ b/src/main/java/org/paysim/paysim/base/StepActionProfile.java @@ -1,8 +1,8 @@ -package paysim.base; +package org.paysim.paysim.base; import java.util.ArrayList; -import paysim.output.Output; +import org.paysim.paysim.output.Output; public class StepActionProfile { private final String action; diff --git a/src/paysim/base/Transaction.java b/src/main/java/org/paysim/paysim/base/Transaction.java similarity index 97% rename from src/paysim/base/Transaction.java rename to src/main/java/org/paysim/paysim/base/Transaction.java index 3414eca..904ef17 100644 --- a/src/paysim/base/Transaction.java +++ b/src/main/java/org/paysim/paysim/base/Transaction.java @@ -1,9 +1,9 @@ -package paysim.base; +package org.paysim.paysim.base; import java.io.Serializable; import java.util.ArrayList; -import paysim.output.Output; +import org.paysim.paysim.output.Output; public class Transaction implements Serializable { private static final long serialVersionUID = 1L; diff --git a/src/paysim/output/Aggregator.java b/src/main/java/org/paysim/paysim/output/Aggregator.java similarity index 94% rename from src/paysim/output/Aggregator.java rename to src/main/java/org/paysim/paysim/output/Aggregator.java index 9fb4562..6dffce4 100644 --- a/src/paysim/output/Aggregator.java +++ b/src/main/java/org/paysim/paysim/output/Aggregator.java @@ -1,4 +1,4 @@ -package paysim.output; +package org.paysim.paysim.output; import java.math.BigDecimal; import java.util.ArrayList; @@ -6,10 +6,10 @@ import java.util.Map; import java.util.stream.Collectors; -import paysim.parameters.ActionTypes; +import org.paysim.paysim.parameters.ActionTypes; -import paysim.base.Transaction; -import paysim.base.StepActionProfile; +import org.paysim.paysim.base.Transaction; +import org.paysim.paysim.base.StepActionProfile; class Aggregator { private static final int DOUBLE_PRECISION = 2; diff --git a/src/paysim/output/Output.java b/src/main/java/org/paysim/paysim/output/Output.java similarity index 94% rename from src/paysim/output/Output.java rename to src/main/java/org/paysim/paysim/output/Output.java index bb16439..50f480a 100644 --- a/src/paysim/output/Output.java +++ b/src/main/java/org/paysim/paysim/output/Output.java @@ -1,4 +1,4 @@ -package paysim.output; +package org.paysim.paysim.output; import java.io.BufferedWriter; import java.io.File; @@ -7,14 +7,14 @@ import java.util.ArrayList; import java.util.Map; -import paysim.PaySim; -import paysim.base.StepActionProfile; -import paysim.base.ClientActionProfile; -import paysim.base.Transaction; -import paysim.actors.Fraudster; -import paysim.parameters.Parameters; -import paysim.parameters.StepsProfiles; -import paysim.utils.DatabaseHandler; +import org.paysim.paysim.PaySim; +import org.paysim.paysim.base.StepActionProfile; +import org.paysim.paysim.base.ClientActionProfile; +import org.paysim.paysim.base.Transaction; +import org.paysim.paysim.actors.Fraudster; +import org.paysim.paysim.parameters.Parameters; +import org.paysim.paysim.parameters.StepsProfiles; +import org.paysim.paysim.utils.DatabaseHandler; public class Output { public static final int PRECISION_OUTPUT = 2; diff --git a/src/paysim/output/SummaryBuilder.java b/src/main/java/org/paysim/paysim/output/SummaryBuilder.java similarity index 95% rename from src/paysim/output/SummaryBuilder.java rename to src/main/java/org/paysim/paysim/output/SummaryBuilder.java index c50116a..593e7fe 100644 --- a/src/paysim/output/SummaryBuilder.java +++ b/src/main/java/org/paysim/paysim/output/SummaryBuilder.java @@ -1,11 +1,11 @@ -package paysim.output; +package org.paysim.paysim.output; import java.util.*; import java.util.function.Function; -import paysim.base.StepActionProfile; -import paysim.parameters.ActionTypes; -import paysim.parameters.StepsProfiles; +import org.paysim.paysim.base.StepActionProfile; +import org.paysim.paysim.parameters.ActionTypes; +import org.paysim.paysim.parameters.StepsProfiles; class SummaryBuilder { private static final String SEPARATOR = "----------------------------------------------------"; diff --git a/src/paysim/parameters/ActionTypes.java b/src/main/java/org/paysim/paysim/parameters/ActionTypes.java similarity index 95% rename from src/paysim/parameters/ActionTypes.java rename to src/main/java/org/paysim/paysim/parameters/ActionTypes.java index 28c5ba3..d42d772 100644 --- a/src/paysim/parameters/ActionTypes.java +++ b/src/main/java/org/paysim/paysim/parameters/ActionTypes.java @@ -1,4 +1,4 @@ -package paysim.parameters; +package org.paysim.paysim.parameters; import java.util.HashMap; import java.util.Map; @@ -6,7 +6,7 @@ import java.util.TreeSet; import java.util.ArrayList; -import paysim.utils.CSVReader; +import org.paysim.paysim.utils.CSVReader; public class ActionTypes { private static final int COLUMN_ACTION = 0, COLUMN_OCCURRENCES = 1; diff --git a/src/paysim/parameters/BalancesClients.java b/src/main/java/org/paysim/paysim/parameters/BalancesClients.java similarity index 95% rename from src/paysim/parameters/BalancesClients.java rename to src/main/java/org/paysim/paysim/parameters/BalancesClients.java index e3abecc..345572f 100644 --- a/src/paysim/parameters/BalancesClients.java +++ b/src/main/java/org/paysim/paysim/parameters/BalancesClients.java @@ -1,4 +1,4 @@ -package paysim.parameters; +package org.paysim.paysim.parameters; import java.util.ArrayList; import java.util.InputMismatchException; @@ -7,8 +7,8 @@ import ec.util.MersenneTwisterFast; -import paysim.utils.CSVReader; -import paysim.utils.RandomCollection; +import org.paysim.paysim.utils.CSVReader; +import org.paysim.paysim.utils.RandomCollection; public class BalancesClients { private static final int COLUMN_LOW = 0, COLUMN_HIGH = 1, COLUMN_PROB = 2; diff --git a/src/paysim/parameters/ClientsProfiles.java b/src/main/java/org/paysim/paysim/parameters/ClientsProfiles.java similarity index 92% rename from src/paysim/parameters/ClientsProfiles.java rename to src/main/java/org/paysim/paysim/parameters/ClientsProfiles.java index 994c268..ba913e5 100644 --- a/src/paysim/parameters/ClientsProfiles.java +++ b/src/main/java/org/paysim/paysim/parameters/ClientsProfiles.java @@ -1,4 +1,4 @@ -package paysim.parameters; +package org.paysim.paysim.parameters; import java.util.ArrayList; import java.util.Collection; @@ -7,9 +7,9 @@ import ec.util.MersenneTwisterFast; -import paysim.base.ClientActionProfile; -import paysim.utils.CSVReader; -import paysim.utils.RandomCollection; +import org.paysim.paysim.base.ClientActionProfile; +import org.paysim.paysim.utils.CSVReader; +import org.paysim.paysim.utils.RandomCollection; public class ClientsProfiles { private static final int COLUMN_ACTION = 0, COLUMN_LOW = 1, COLUMN_HIGH = 2, COLUMN_AVG = 3, COLUMN_STD = 4, COLUMN_FREQ = 5; diff --git a/src/paysim/parameters/Parameters.java b/src/main/java/org/paysim/paysim/parameters/Parameters.java similarity index 95% rename from src/paysim/parameters/Parameters.java rename to src/main/java/org/paysim/paysim/parameters/Parameters.java index 30ad13a..a6bbb43 100644 --- a/src/paysim/parameters/Parameters.java +++ b/src/main/java/org/paysim/paysim/parameters/Parameters.java @@ -1,10 +1,10 @@ -package paysim.parameters; +package org.paysim.paysim.parameters; import java.io.FileInputStream; import java.util.ArrayList; import java.util.Properties; -import paysim.output.Output; +import org.paysim.paysim.output.Output; public class Parameters { private static String seedString; @@ -12,7 +12,7 @@ public class Parameters { public static double multiplier, fraudProbability, transferLimit; public static String aggregatedTransactions, maxOccurrencesPerClient, initialBalancesDistribution, overdraftLimits, clientsProfilesFile, transactionsTypes; - public static String outputPath; + public static String typologiesFolder, outputPath; public static boolean saveToDB; public static String dbUrl, dbUser, dbPassword; @@ -54,6 +54,7 @@ private static void loadPropertiesFile(String propertiesFile) { overdraftLimits = parameters.getProperty("overdraftLimits"); clientsProfilesFile = parameters.getProperty("clientsProfiles"); + typologiesFolder = parameters.getProperty("typologiesFolder"); outputPath = parameters.getProperty("outputPath"); saveToDB = parameters.getProperty("saveToDB").equals("1"); diff --git a/src/paysim/parameters/StepsProfiles.java b/src/main/java/org/paysim/paysim/parameters/StepsProfiles.java similarity index 95% rename from src/paysim/parameters/StepsProfiles.java rename to src/main/java/org/paysim/paysim/parameters/StepsProfiles.java index 9dc9ab2..dd67fc1 100644 --- a/src/paysim/parameters/StepsProfiles.java +++ b/src/main/java/org/paysim/paysim/parameters/StepsProfiles.java @@ -1,4 +1,4 @@ -package paysim.parameters; +package org.paysim.paysim.parameters; import java.util.ArrayList; import java.util.Collections; @@ -7,11 +7,9 @@ import java.util.function.Function; import java.util.stream.Collectors; -import paysim.utils.CSVReader; +import org.paysim.paysim.utils.CSVReader; -import static paysim.parameters.ActionTypes.isValidAction; - -import paysim.base.StepActionProfile; +import org.paysim.paysim.base.StepActionProfile; public class StepsProfiles { @@ -33,7 +31,7 @@ public StepsProfiles(String filename, double multiplier, int nbSteps) { stepTargetCount = new ArrayList<>(Collections.nCopies(nbSteps, 0)); for (String[] line : parameters) { - if (isValidAction(line[COLUMN_ACTION])) { + if (ActionTypes.isValidAction(line[COLUMN_ACTION])) { int step = Integer.parseInt(line[COLUMN_STEP]); int count = Integer.parseInt(line[COLUMN_COUNT]); diff --git a/src/main/java/org/paysim/paysim/parameters/TypologiesFiles.java b/src/main/java/org/paysim/paysim/parameters/TypologiesFiles.java new file mode 100644 index 0000000..998ff8a --- /dev/null +++ b/src/main/java/org/paysim/paysim/parameters/TypologiesFiles.java @@ -0,0 +1,5 @@ +package org.paysim.paysim.parameters; + +public class TypologiesFiles { + public static final String drugNetworkOne = "DrugNetworkOne.graphml"; +} diff --git a/src/paysim/utils/CSVReader.java b/src/main/java/org/paysim/paysim/utils/CSVReader.java similarity index 95% rename from src/paysim/utils/CSVReader.java rename to src/main/java/org/paysim/paysim/utils/CSVReader.java index 007788b..bb75123 100644 --- a/src/paysim/utils/CSVReader.java +++ b/src/main/java/org/paysim/paysim/utils/CSVReader.java @@ -1,4 +1,4 @@ -package paysim.utils; +package org.paysim.paysim.utils; import java.io.BufferedReader; import java.io.FileReader; diff --git a/src/paysim/utils/DatabaseHandler.java b/src/main/java/org/paysim/paysim/utils/DatabaseHandler.java similarity index 91% rename from src/paysim/utils/DatabaseHandler.java rename to src/main/java/org/paysim/paysim/utils/DatabaseHandler.java index 97653d5..0c926b9 100644 --- a/src/paysim/utils/DatabaseHandler.java +++ b/src/main/java/org/paysim/paysim/utils/DatabaseHandler.java @@ -1,6 +1,6 @@ -package paysim.utils; +package org.paysim.paysim.utils; -import paysim.base.Transaction; +import org.paysim.paysim.base.Transaction; import java.sql.DriverManager; @@ -27,7 +27,7 @@ public DatabaseHandler(String url, String user, String password) { public void insert(String simulatorName, Transaction trans) { try { - String sql = "INSERT INTO paysim.paysimLog (logName, pType, pAmount, cliFrom,pOldBalanceFrom,pNewBalanceFrom," + String sql = "INSERT INTO org.paysim.paysim.paysimLog (logName, pType, pAmount, cliFrom,pOldBalanceFrom,pNewBalanceFrom," + "cliTo,pOldBalanceTo,pNewBalanceTo,isFraud,isFlaggedFraud,step) " + "VALUES (?,?,?,?,?,?,?,?,?,?,?,?);"; PreparedStatement st = con.prepareStatement(sql); diff --git a/src/main/java/org/paysim/paysim/utils/GraphUtils.java b/src/main/java/org/paysim/paysim/utils/GraphUtils.java new file mode 100644 index 0000000..4f4e324 --- /dev/null +++ b/src/main/java/org/paysim/paysim/utils/GraphUtils.java @@ -0,0 +1,73 @@ +package org.paysim.paysim.utils; + +import java.io.*; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; + +import org.apache.tinkerpop.gremlin.structure.*; +import org.apache.tinkerpop.gremlin.structure.io.graphml.GraphMLReader; +import org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerGraph; + +public class GraphUtils { + + public static Graph loadFromFile(String filename) { + Graph graph = TinkerGraph.open(); + try { + InputStream targetStream = new FileInputStream(filename); + GraphMLReader.build().create() + .readGraph(targetStream, graph); + } catch (IOException e) { + e.printStackTrace(); + } + return graph; + } + + public static Vertex getVertex(Graph graph, String vertexName){ + Iterator vertices = graph.vertices(vertexName); + if (vertices.hasNext()){ + return vertices.next(); + } else { + throw new NoSuchElementException("Vertex was not found"); + } + } + + public static Edge getEdge(Graph graph, String edgeName){ + Iterator edges = graph.edges(edgeName); + if (edges.hasNext()){ + return edges.next(); + } else { + throw new NoSuchElementException("Edge was not found"); + } + } + + public static Object getProperty(Vertex vertex, String propName){ + Iterator> properties = vertex.properties(propName); + if (properties.hasNext()){ + return properties.next().value(); + } else { + throw new NoSuchElementException("Property was not found"); + } + } + + public static Object getProperty(Edge edge, String propName){ + Iterator> properties = edge.properties(propName); + if (properties.hasNext()){ + return properties.next().value(); + } else { + throw new NoSuchElementException("Property was not found"); + } + } + + public static Map unserializeMap(String serializedMap){ + Map map = new HashMap<>(); + String[] pairs = serializedMap.split(","); + for (int i = 0; i < pairs.length; i++) { + String pair = pairs[i]; + String[] keyValue = pair.split(":"); + map.put(Double.valueOf(keyValue[0]), Double.valueOf(keyValue[1])); + } + return map; + } +} diff --git a/src/paysim/utils/RandomCollection.java b/src/main/java/org/paysim/paysim/utils/RandomCollection.java similarity index 88% rename from src/paysim/utils/RandomCollection.java rename to src/main/java/org/paysim/paysim/utils/RandomCollection.java index 86a8705..e885618 100644 --- a/src/paysim/utils/RandomCollection.java +++ b/src/main/java/org/paysim/paysim/utils/RandomCollection.java @@ -1,4 +1,4 @@ -package paysim.utils; +package org.paysim.paysim.utils; import java.util.Collection; import java.util.NavigableMap; @@ -30,6 +30,10 @@ public E next() { if (this.random == null) { throw new NullPointerException("The RNG must be initialized to pick a random element."); } + if (this.map.isEmpty()){ + throw new IllegalStateException("The collection is empty"); + } + double value = random.nextDouble() * total; return map.higherEntry(value).getValue(); }