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
+
+
+
+
+
+
+ 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();
}