Skip to content

Commit 32c248a

Browse files
committed
Adds basic DrugNetwork typology
Also adds - an anti-drift mechanism by attaching a spring to the balance of the accounts (if we consider the balance of an account as a biased Bernoulli walk) - a way to load graph data for typologies
1 parent dc8192f commit 32c248a

13 files changed

+375
-47
lines changed

PaySim.properties

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ clientsProfiles=./paramFiles/clientsProfiles.csv
1414
initialBalancesDistribution=./paramFiles/initialBalancesDistribution.csv
1515
overdraftLimits=./paramFiles/overdraftLimits.csv
1616
maxOccurrencesPerClient=./paramFiles/maxOccurrencesPerClient.csv
17+
typologiesFolder=./paramFiles/typologies/
1718
outputPath=./outputs/
1819
saveToDB=0
1920
dbUrl=jdbc:mysql://localhost:3306/paysim
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
4+
5+
6+
<key id="count" for="node" attr.name="count" attr.type="int"/>
7+
<key id="monthlySpending" for="node" attr.name="monthlySpending" attr.type="double"/>
8+
<key id="thresholdForCashOut" for="node" attr.name="thresholdForCashOut" attr.type="double"/>
9+
10+
<key id="action" for="edge" attr.name="action" attr.type="string"/>
11+
<key id="amount" for="edge" attr.name="amount" attr.type="double"/>
12+
<key id="probAmountProfile" for="edge" attr.name="probAmountProfile" attr.type="string"/>
13+
14+
<graph id="DrugNetworkOne" edgedefault="directed">
15+
<node id="DrugDealer">
16+
<data key="thresholdForCashOut">1000</data>
17+
</node>
18+
<node id="DrugConsumers">
19+
<data key="count">60</data>
20+
<data key="monthlySpending">100</data>
21+
</node>
22+
<node id="CashSink"/>
23+
<edge id="BuyDrugs" source="DrugConsumers" target="DrugDealer">
24+
<data key="action">TRANSFER</data>
25+
<data key="probAmountProfile">10:0.25, 20:0.50, 50:0.20, 200:0.05</data>
26+
</edge>
27+
<edge id="CashOutProfit" source="DrugDealer" target="CashSink">
28+
<data key="action">CASH_OUT</data>
29+
<data key="amount">1000</data>
30+
</edge>
31+
</graph>
32+
</graphml>

src/paysim/PaySim.java

+14-11
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import paysim.actors.Client;
1414
import paysim.actors.Fraudster;
1515
import paysim.actors.Merchant;
16+
import paysim.actors.networkdrugs.NetworkDrug;
1617

1718
import paysim.base.Transaction;
1819
import paysim.base.ClientActionProfile;
@@ -141,22 +142,20 @@ private void initActors() {
141142
//Add the clients
142143
System.out.println("NbClients: " + (int) (Parameters.nbClients * Parameters.multiplier));
143144
for (int i = 0; i < Parameters.nbClients * Parameters.multiplier; i++) {
144-
Client c = new Client(generateId(),
145-
pickRandomBank(),
146-
pickNextClientProfile(),
147-
BalancesClients.pickNextBalance(random),
148-
random,
149-
Parameters.stepsProfiles.getTotalTargetCount());
145+
Client c = new Client(this);
150146
clients.add(c);
151147
}
152148

153-
//Schedule clients to act at each step of the simulation
149+
NetworkDrug.createNetwork(this, Parameters.typologiesFolder + TypologiesFiles.drugNetworkOne);
150+
151+
// Do not write code under this part otherwise clients will not be used in simulation
152+
// Schedule clients to act at each step of the simulation
154153
for (Client c : clients) {
155154
schedule.scheduleRepeating(c);
156155
}
157156
}
158157

159-
private Map<String, ClientActionProfile> pickNextClientProfile() {
158+
public Map<String, ClientActionProfile> pickNextClientProfile() {
160159
Map<String, ClientActionProfile> profile = new HashMap<>();
161160
for (String action : ActionTypes.getActions()) {
162161
ClientActionProfile clientActionProfile = Parameters.clientsProfiles.pickNextActionProfile(action);
@@ -218,7 +217,7 @@ public Client pickRandomClient(String nameOrig) {
218217
Client clientDest = null;
219218

220219
String nameDest = nameOrig;
221-
while (nameOrig.equals(nameDest)){
220+
while (nameOrig.equals(nameDest)) {
222221
clientDest = clients.get(random.nextInt(clients.size()));
223222
nameDest = clientDest.getName();
224223
}
@@ -241,15 +240,19 @@ public ArrayList<Client> getClients() {
241240
return clients;
242241
}
243242

243+
public void addClient(Client c) {
244+
clients.add(c);
245+
}
246+
244247
public int getStepTargetCount() {
245248
return Parameters.stepsProfiles.getTargetCount(currentStep);
246249
}
247250

248-
public Map<String, Double> getStepProbabilities(){
251+
public Map<String, Double> getStepProbabilities() {
249252
return Parameters.stepsProfiles.getProbabilitiesPerStep(currentStep);
250253
}
251254

252-
public StepActionProfile getStepAction(String action){
255+
public StepActionProfile getStepAction(String action) {
253256
return Parameters.stepsProfiles.getActionForStep(currentStep, action);
254257
}
255258
}

src/paysim/actors/Client.java

+89-28
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
11
package paysim.actors;
22

3+
import java.util.Arrays;
4+
import java.util.HashMap;
35
import java.util.Map;
46
import static java.lang.Math.max;
57

68
import ec.util.MersenneTwisterFast;
7-
import paysim.parameters.ActionTypes;
8-
import paysim.parameters.BalancesClients;
99
import sim.engine.SimState;
1010
import sim.engine.Steppable;
1111
import sim.util.distribution.Binomial;
1212

1313
import paysim.PaySim;
14+
1415
import paysim.base.ClientActionProfile;
1516
import paysim.base.ClientProfile;
1617
import paysim.base.StepActionProfile;
1718
import paysim.base.Transaction;
19+
20+
import paysim.parameters.ActionTypes;
1821
import paysim.parameters.Parameters;
22+
import paysim.parameters.BalancesClients;
23+
1924
import paysim.utils.RandomCollection;
2025

2126

@@ -29,20 +34,22 @@ public class Client extends SuperActor implements Steppable {
2934
private double clientWeight;
3035
private double balanceMax = 0;
3136
private int countTransferTransactions = 0;
37+
private double expectedAvgTransaction = 0;
38+
private double initialBalance;
3239

3340
Client(String name, Bank bank) {
3441
super(CLIENT_IDENTIFIER + name);
3542
this.bank = bank;
3643
}
3744

38-
public Client(String name, Bank bank, Map<String, ClientActionProfile> profile, double initBalance,
39-
MersenneTwisterFast random, int totalTargetCount) {
40-
super(CLIENT_IDENTIFIER + name);
41-
this.bank = bank;
42-
this.clientProfile = new ClientProfile(profile, random);
43-
this.clientWeight = ((double) clientProfile.getClientTargetCount()) / totalTargetCount;
44-
this.balance = initBalance;
45-
this.overdraftLimit = pickOverdraftLimit(random);
45+
public Client(PaySim paySim) {
46+
super(CLIENT_IDENTIFIER + paySim.generateId());
47+
this.bank = paySim.pickRandomBank();
48+
this.clientProfile = new ClientProfile(paySim.pickNextClientProfile(), paySim.random);
49+
this.clientWeight = ((double) clientProfile.getClientTargetCount()) / Parameters.stepsProfiles.getTotalTargetCount();
50+
this.initialBalance = BalancesClients.pickNextBalance(paySim.random);
51+
this.balance = initialBalance;
52+
this.overdraftLimit = pickOverdraftLimit(paySim.random);
4653
}
4754

4855
@Override
@@ -74,26 +81,80 @@ private int pickCount(MersenneTwisterFast random, int targetStepCount) {
7481

7582
private String pickAction(MersenneTwisterFast random, Map<String, Double> stepActionProb) {
7683
Map<String, Double> clientProbabilities = clientProfile.getActionProbability();
84+
Map<String, Double> rawProbabilities = new HashMap<>();
7785
RandomCollection<String> actionPicker = new RandomCollection<>(random);
7886

87+
// Pick the compromise between the Step distribution and the Client distribution
7988
for (Map.Entry<String, Double> clientEntry : clientProbabilities.entrySet()) {
8089
String action = clientEntry.getKey();
8190
double clientProbability = clientEntry.getValue();
82-
double finalProbability;
91+
double rawProbability;
8392

8493
if (stepActionProb.containsKey(action)) {
8594
double stepProbability = stepActionProb.get(action);
8695

87-
finalProbability = (clientProbability + stepProbability) / 2;
96+
rawProbability = (clientProbability + stepProbability) / 2;
8897
} else {
89-
finalProbability = clientProbability;
98+
rawProbability = clientProbability;
99+
}
100+
rawProbabilities.put(action, rawProbability);
101+
}
102+
103+
// Correct the distribution so the balance of the account do not diverge too much
104+
double probInflow = 0;
105+
for (Map.Entry<String, Double> rawEntry : rawProbabilities.entrySet()) {
106+
String action = rawEntry.getKey();
107+
if (isInflow(action)) {
108+
probInflow += rawEntry.getValue();
109+
}
110+
}
111+
double probOutflow = 1 - probInflow;
112+
double newProbInflow = computeProbWithSpring(probInflow, probOutflow, balance);
113+
double newProbOutflow = 1 - newProbInflow;
114+
115+
for (Map.Entry<String, Double> rawEntry : rawProbabilities.entrySet()) {
116+
String action = rawEntry.getKey();
117+
double rawProbability = rawEntry.getValue();
118+
double finalProbability;
119+
120+
if (isInflow(action)) {
121+
finalProbability = rawProbability * newProbInflow / probInflow;
122+
} else {
123+
finalProbability = rawProbability * newProbOutflow / probOutflow;
90124
}
91125
actionPicker.add(finalProbability, action);
92126
}
93127

94128
return actionPicker.next();
95129
}
96130

131+
/**
132+
* The Biased Bernoulli Walk we were doing can go far to the equilibrium of an account
133+
* To avoid this we conceptually add a spring that would be attached to the equilibrium position of the account
134+
*/
135+
private double computeProbWithSpring(double probUp, double probDown, double currentBalance){
136+
double equilibrium = 40 * expectedAvgTransaction; // Could also be the initial balance in other models
137+
double correctionStrength = 3 * Math.pow(10, -5); // In a physical model it would be 1 / 2 * kB * T
138+
double characteristicLengthSpring = equilibrium;
139+
double k = 1 / characteristicLengthSpring;
140+
double springForce = k * (equilibrium - currentBalance);
141+
double newProbUp = 0.5d * ( 1d + (expectedAvgTransaction * correctionStrength) * springForce + (probUp - probDown));
142+
143+
if (newProbUp > 1){
144+
newProbUp = 1;
145+
} else if (newProbUp < 0){
146+
newProbUp = 0;
147+
}
148+
return newProbUp;
149+
150+
}
151+
152+
private boolean isInflow(String action){
153+
String[] inflowActions = {CASH_IN, DEPOSIT};
154+
return Arrays.stream(inflowActions)
155+
.anyMatch(action::equals);
156+
}
157+
97158
private double pickAmount(MersenneTwisterFast random, String action, StepActionProfile stepAmountProfile) {
98159
ClientActionProfile clientAmountProfile = clientProfile.getProfilePerAction(action);
99160

@@ -135,7 +196,7 @@ private void makeTransaction(PaySim state, int step, String action, double amoun
135196
double reducedAmount = amount;
136197
boolean lastTransferFailed = false;
137198
while (reducedAmount > Parameters.transferLimit && !lastTransferFailed) {
138-
lastTransferFailed = handleTransfer(state, step, Parameters.transferLimit, clientTo);
199+
lastTransferFailed = !handleTransfer(state, step, Parameters.transferLimit, clientTo);
139200
reducedAmount -= Parameters.transferLimit;
140201
}
141202
if (reducedAmount > 0 && !lastTransferFailed) {
@@ -150,7 +211,7 @@ private void makeTransaction(PaySim state, int step, String action, double amoun
150211
}
151212
}
152213

153-
private void handleCashIn(PaySim paysim, int step, double amount) {
214+
protected void handleCashIn(PaySim paysim, int step, double amount) {
154215
Merchant merchantTo = paysim.pickRandomMerchant();
155216
String nameOrig = this.getName();
156217
String nameDest = merchantTo.getName();
@@ -167,7 +228,7 @@ private void handleCashIn(PaySim paysim, int step, double amount) {
167228
paysim.getTransactions().add(t);
168229
}
169230

170-
private void handleCashOut(PaySim paysim, int step, double amount) {
231+
protected void handleCashOut(PaySim paysim, int step, double amount) {
171232
Merchant merchantTo = paysim.pickRandomMerchant();
172233
String nameOrig = this.getName();
173234
String nameDest = merchantTo.getName();
@@ -187,7 +248,7 @@ private void handleCashOut(PaySim paysim, int step, double amount) {
187248
paysim.getTransactions().add(t);
188249
}
189250

190-
private void handleDebit(PaySim paysim, int step, double amount) {
251+
protected void handleDebit(PaySim paysim, int step, double amount) {
191252
String nameOrig = this.getName();
192253
String nameDest = this.bank.getName();
193254
double oldBalanceOrig = this.getBalance();
@@ -205,7 +266,7 @@ private void handleDebit(PaySim paysim, int step, double amount) {
205266
paysim.getTransactions().add(t);
206267
}
207268

208-
private void handlePayment(PaySim paysim, int step, double amount) {
269+
protected void handlePayment(PaySim paysim, int step, double amount) {
209270
Merchant merchantTo = paysim.pickRandomMerchant();
210271

211272
String nameOrig = this.getName();
@@ -228,17 +289,17 @@ private void handlePayment(PaySim paysim, int step, double amount) {
228289
paysim.getTransactions().add(t);
229290
}
230291

231-
boolean handleTransfer(PaySim paysim, int step, double amount, Client clientTo) {
292+
protected boolean handleTransfer(PaySim paysim, int step, double amount, Client clientTo) {
232293
String nameOrig = this.getName();
233294
String nameDest = clientTo.getName();
234295
double oldBalanceOrig = this.getBalance();
235296
double oldBalanceDest = clientTo.getBalance();
236297

237-
boolean transferFailed;
298+
boolean transferSuccessful;
238299
if (!isDetectedAsFraud(amount)) {
239300
boolean isUnauthorizedOverdraft = this.withdraw(amount);
240-
transferFailed = isUnauthorizedOverdraft;
241-
if (!isUnauthorizedOverdraft) {
301+
transferSuccessful = !isUnauthorizedOverdraft;
302+
if (transferSuccessful) {
242303
clientTo.deposit(amount);
243304
}
244305

@@ -252,7 +313,7 @@ boolean handleTransfer(PaySim paysim, int step, double amount, Client clientTo)
252313
t.setFraud(this.isFraud());
253314
paysim.getTransactions().add(t);
254315
} else { // create the transaction but don't move any money as the transaction was detected as fraudulent
255-
transferFailed = true;
316+
transferSuccessful = false;
256317
double newBalanceOrig = this.getBalance();
257318
double newBalanceDest = clientTo.getBalance();
258319

@@ -263,10 +324,10 @@ boolean handleTransfer(PaySim paysim, int step, double amount, Client clientTo)
263324
t.setFraud(this.isFraud());
264325
paysim.getTransactions().add(t);
265326
}
266-
return transferFailed;
327+
return transferSuccessful;
267328
}
268329

269-
private void handleDeposit(PaySim paysim, int step, double amount) {
330+
protected void handleDeposit(PaySim paysim, int step, double amount) {
270331
String nameOrig = this.getName();
271332
String nameDest = this.bank.getName();
272333
double oldBalanceOrig = this.getBalance();
@@ -297,17 +358,17 @@ private boolean isDetectedAsFraud(double amount) {
297358
}
298359

299360
private double pickOverdraftLimit(MersenneTwisterFast random){
300-
double averageTransaction = 0, stdTransaction = 0;
361+
double stdTransaction = 0;
301362

302363
for (String action: ActionTypes.getActions()){
303364
double actionProbability = clientProfile.getActionProbability().get(action);
304365
ClientActionProfile actionProfile = clientProfile.getProfilePerAction(action);
305-
averageTransaction += actionProfile.getAvgAmount() * actionProbability;
366+
expectedAvgTransaction += actionProfile.getAvgAmount() * actionProbability;
306367
stdTransaction += Math.pow(actionProfile.getStdAmount() * actionProbability, 2);
307368
}
308369
stdTransaction = Math.sqrt(stdTransaction);
309370

310-
double randomizedMeanTransaction = random.nextGaussian() * stdTransaction + averageTransaction;
371+
double randomizedMeanTransaction = random.nextGaussian() * stdTransaction + expectedAvgTransaction;
311372

312373
return BalancesClients.getOverdraftLimit(randomizedMeanTransaction);
313374
}

src/paysim/actors/Fraudster.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import paysim.PaySim;
99
import paysim.parameters.Parameters;
10+
1011
import paysim.output.Output;
1112

1213
public class Fraudster extends SuperActor implements Steppable {
@@ -34,17 +35,17 @@ public void step(SimState state) {
3435
Mule muleClient = new Mule(paysim.generateId(), paysim.pickRandomBank());
3536
muleClient.setFraud(true);
3637
if (balance > Parameters.transferLimit) {
37-
transferFailed = c.handleTransfer(paysim, step, Parameters.transferLimit, muleClient);
38+
transferFailed = !c.handleTransfer(paysim, step, Parameters.transferLimit, muleClient);
3839
balance -= Parameters.transferLimit;
3940
} else {
40-
transferFailed = c.handleTransfer(paysim, step, balance, muleClient);
41+
transferFailed = !c.handleTransfer(paysim, step, balance, muleClient);
4142
balance = 0;
4243
}
4344

4445
profit += muleClient.getBalance();
4546
muleClient.fraudulentCashOut(paysim, step, muleClient.getBalance());
4647
nbVictims++;
47-
paysim.getClients().add(muleClient);
48+
paysim.addClient(muleClient);
4849
if (transferFailed)
4950
break;
5051
}

0 commit comments

Comments
 (0)