-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathevo.js
More file actions
133 lines (114 loc) · 3.46 KB
/
Copy pathevo.js
File metadata and controls
133 lines (114 loc) · 3.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/**
* Evolutionary dynamics on graphs for 2-strategy games (C/D).
* Strategies: 1 = C, 0 = D
*
* Payoff matrix:
* vs C vs D
* C R S
* D T P
*
* Fitness mapping (weak selection):
* f = 1 - w + w * payoff (requires w in [0,1] typically)
*
* Imitation (Fermi):
* P(i adopts j) = 1/(1 + exp(-beta*(pj - pi)))
*/
export function donationToMatrix(b, c) {
return { R: b - c, S: -c, T: b, P: 0 };
}
export function computePayoffs(g, strat, M) {
const N = g.N;
const pay = new Float64Array(N);
for (let i = 0; i < N; i++) {
const si = strat[i];
let sum = 0;
const nei = g.adj[i];
for (let k = 0; k < nei.length; k++) {
const j = nei[k];
const sj = strat[j];
if (si === 1 && sj === 1) sum += M.R;
else if (si === 1 && sj === 0) sum += M.S;
else if (si === 0 && sj === 1) sum += M.T;
else sum += M.P;
}
pay[i] = sum;
}
return pay;
}
function pickWeightedIndex(weights, rng) {
let total = 0;
for (let i = 0; i < weights.length; i++) total += weights[i];
if (total <= 0) return rng.int(weights.length);
let r = rng.random() * total;
for (let i = 0; i < weights.length; i++) {
r -= weights[i];
if (r <= 0) return i;
}
return weights.length - 1;
}
export function stepDB(g, strat, M, w, mu, rng) {
// Death–Birth: choose random node to die, neighbors compete proportional to fitness to fill it.
const N = g.N;
const pay = computePayoffs(g, strat, M);
const dead = rng.int(N);
const neigh = g.adj[dead];
if (neigh.length === 0) return 1; // isolated node
const fit = new Float64Array(neigh.length);
for (let i = 0; i < neigh.length; i++) {
const j = neigh[i];
const fj = (1 - w) + w * pay[j];
fit[i] = Math.max(0, fj);
}
const winnerIdx = pickWeightedIndex(fit, rng);
const parent = neigh[winnerIdx];
let childStrat = strat[parent];
// mutation (optional)
if (mu > 0 && rng.random() < mu) childStrat = 1 - childStrat;
strat[dead] = childStrat;
return 1;
}
export function stepBD(g, strat, M, w, mu, rng) {
// Birth–Death: choose reproducer proportional to fitness, then random neighbor replaced.
const N = g.N;
const pay = computePayoffs(g, strat, M);
const fitAll = new Float64Array(N);
for (let i = 0; i < N; i++) {
const fi = (1 - w) + w * pay[i];
fitAll[i] = Math.max(0, fi);
}
const parent = pickWeightedIndex(fitAll, rng);
const neigh = g.adj[parent];
if (neigh.length === 0) return 1;
const dead = neigh[rng.int(neigh.length)];
let childStrat = strat[parent];
if (mu > 0 && rng.random() < mu) childStrat = 1 - childStrat;
strat[dead] = childStrat;
return 1;
}
export function stepImitationFermi(g, strat, M, beta, mu, rng) {
// Pick random focal i, pick random neighbor j, i adopts j with Fermi probability
const N = g.N;
const pay = computePayoffs(g, strat, M);
const i = rng.int(N);
const neigh = g.adj[i];
if (neigh.length === 0) return 1;
const j = neigh[rng.int(neigh.length)];
const pi = pay[i], pj = pay[j];
const pAdopt = 1 / (1 + Math.exp(-beta * (pj - pi)));
if (rng.random() < pAdopt) {
let s = strat[j];
if (mu > 0 && rng.random() < mu) s = 1 - s;
strat[i] = s;
}
return 1;
}
export function countCooperators(strat) {
let c = 0;
for (let i = 0; i < strat.length; i++) c += (strat[i] === 1 ? 1 : 0);
return c;
}
export function isFixated(strat) {
const first = strat[0];
for (let i = 1; i < strat.length; i++) if (strat[i] !== first) return false;
return true;
}