@anonympins/fingerprint 0.3.8 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +331 -256
- package/README.md +62 -53
- package/composer.json +38 -38
- package/index.js +4 -4
- package/package.json +103 -103
- package/phpunit.xml +20 -20
- package/public/fp.js +1 -1
- package/public/fp.wasm +0 -0
- package/src/js/build-client.js +1 -1
- package/src/js/fingerprint.client.js +2 -0
- package/src/js/fingerprint.js +4429 -4381
- package/src/js/mongodb-store.js +79 -79
- package/src/js/pow.solver.inline.js +31 -0
- package/src/js/pow.solver.js +31 -0
- package/src/js/tests/fingerprint.builder.test.js +79 -0
- package/src/js/tests/fingerprint.client.init.test.js +120 -0
- package/src/js/tests/fingerprint.client.test.js +105 -0
- package/src/js/tests/fingerprint.engine.test.js +371 -0
- package/src/js/tests/fingerprint.isMalicious.test.js +117 -0
- package/src/js/tests/fingerprint.test.js +2319 -0
- package/src/js/tests/ip-reputation.test.js +132 -0
- package/src/js/tests/ja3AnomalyDetector.test.js +135 -0
- package/src/js/tests/library.test.js +96 -0
- package/src/js/tests/metrics.test.js +104 -0
- package/src/js/tests/pow.solver.test.js +198 -0
- package/src/js/tests/problem-manager.test.js +323 -0
- package/src/js/tests/stores.test.js +118 -0
- package/src/php/Challenge/ChallengeUtils.php +361 -361
- package/src/php/Config/SecurityProfiles.php +271 -266
- package/src/php/FingerprintBuilder.php +185 -185
- package/src/php/FingerprintClient.php +131 -131
- package/src/php/FingerprintEngine.php +1006 -1006
- package/src/php/Ja3AnomalyDetector.php +227 -227
- package/src/php/Optimization/FunctionRegistry.php +62 -62
- package/src/php/Optimization/Optimization.php +255 -255
- package/src/php/Optimization/OptimizationOperators.php +304 -304
- package/src/php/Store/InMemoryStore.php +66 -66
- package/src/php/Store/MongoDbStore.php +104 -104
- package/src/php/Store/RedisStore.php +53 -53
- package/src/php/Tests/ChallengeUtilsTest.php +81 -81
- package/src/php/Tests/FingerprintBuilderTest.php +57 -57
- package/src/php/Tests/FingerprintClientTest.php +71 -0
- package/src/php/Tests/FingerprintEngineTest.php +299 -299
- package/src/php/Tests/IpReputationTest.php +156 -156
- package/src/php/Tests/Ja3AnomalyDetectorTest.php +179 -179
- package/src/php/Tests/MetricsTest.php +45 -45
- package/src/php/Tests/PowTest.php +39 -39
- package/src/php/Tests/ProblemManagerTest.php +296 -296
- package/src/php/Tests/RequestUtilsTest.php +253 -145
- package/src/php/Tests/TLSClientHelloParserTest.php +118 -0
- package/src/php/Tests/problems.config.json +8 -8
- package/src/php/Utils/BigInt.php +144 -144
- package/src/php/Utils/Logger.php +29 -29
- package/src/php/Utils/MaliciousPatterns.php +58 -58
- package/src/php/Utils/MetricsManager.php +166 -166
- package/src/php/Utils/RequestUtils.php +1169 -1169
- package/src/php/Utils/TLSClientHelloParser.php +117 -0
- package/src/php/bin/auto-tune.php +117 -117
|
@@ -1,256 +1,256 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
declare(strict_types=1);
|
|
4
|
-
|
|
5
|
-
namespace Anonympins\Fingerprint\Optimization;
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Bibliothèque d'algorithmes d'optimisation.
|
|
9
|
-
*/
|
|
10
|
-
class Optimization
|
|
11
|
-
{
|
|
12
|
-
/**
|
|
13
|
-
* Génère un nombre flottant aléatoire cryptographiquement sûr entre 0 (inclus) et 1 (exclus).
|
|
14
|
-
*/
|
|
15
|
-
private static function secureRandom(): float
|
|
16
|
-
{
|
|
17
|
-
try {
|
|
18
|
-
return random_int(0, PHP_INT_MAX - 1) / PHP_INT_MAX;
|
|
19
|
-
} catch (\Exception $e) {
|
|
20
|
-
return (float)mt_rand() / (float)mt_getrandmax(); // Fallback
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Algorithme génétique multi-objectifs (inspiré de NSGA-II).
|
|
26
|
-
* @param callable $createIndividual
|
|
27
|
-
* @param callable $fitnessFunction
|
|
28
|
-
* @param callable $crossover
|
|
29
|
-
* @param callable $mutate
|
|
30
|
-
* @param array $options
|
|
31
|
-
* @return array<int, array{solution: mixed, objectives: array<float>}>
|
|
32
|
-
*/
|
|
33
|
-
public static function geneticAlgorithmMultiObjective(
|
|
34
|
-
callable $createIndividual,
|
|
35
|
-
callable $fitnessFunction,
|
|
36
|
-
callable $crossover,
|
|
37
|
-
callable $mutate,
|
|
38
|
-
array $options = []
|
|
39
|
-
): array {
|
|
40
|
-
$generations = $options['generations'] ?? 150;
|
|
41
|
-
$populationSize = $options['populationSize'] ?? 60;
|
|
42
|
-
$mutationRate = $options['mutationRate'] ?? 0.1;
|
|
43
|
-
|
|
44
|
-
$population = [];
|
|
45
|
-
for ($i = 0; $i < $populationSize; $i++) {
|
|
46
|
-
$individual = $createIndividual();
|
|
47
|
-
$population[] = [
|
|
48
|
-
'individual' => $individual,
|
|
49
|
-
'objectives' => $fitnessFunction($individual)
|
|
50
|
-
];
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
for ($gen = 0; $gen < $generations; $gen++) {
|
|
54
|
-
// 1. Créer une population d'enfants
|
|
55
|
-
$offspring = [];
|
|
56
|
-
for ($i = 0; $i < $populationSize; $i++) {
|
|
57
|
-
$parent1 = $population[random_int(0, count($population) - 1)];
|
|
58
|
-
$parent2 = $population[random_int(0, count($population) - 1)];
|
|
59
|
-
$childIndividual = $crossover($parent1['individual'], $parent2['individual']);
|
|
60
|
-
if (self::secureRandom() < $mutationRate) {
|
|
61
|
-
$childIndividual = $mutate($childIndividual);
|
|
62
|
-
}
|
|
63
|
-
$offspring[] = [
|
|
64
|
-
'individual' => $childIndividual,
|
|
65
|
-
'objectives' => $fitnessFunction($childIndividual)
|
|
66
|
-
];
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// 2. Combiner parents et enfants
|
|
70
|
-
$combinedPopulation = array_merge($population, $offspring);
|
|
71
|
-
|
|
72
|
-
// 3. Trier la population combinée en fronts
|
|
73
|
-
$fronts = self::nonDominatedSort($combinedPopulation);
|
|
74
|
-
|
|
75
|
-
// 4. Construire la nouvelle population
|
|
76
|
-
$newPopulation = [];
|
|
77
|
-
foreach ($fronts as $front) {
|
|
78
|
-
if (count($newPopulation) + count($front) <= $populationSize) {
|
|
79
|
-
$newPopulation = array_merge($newPopulation, $front);
|
|
80
|
-
} else {
|
|
81
|
-
self::calculateCrowdingDistance($front);
|
|
82
|
-
// Trier par distance décroissante
|
|
83
|
-
usort($front, fn ($a, $b) => $b['crowdingDistance'] <=> $a['crowdingDistance']);
|
|
84
|
-
$remaining = $populationSize - count($newPopulation);
|
|
85
|
-
$newPopulation = array_merge($newPopulation, array_slice($front, 0, $remaining));
|
|
86
|
-
break;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
$population = $newPopulation;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Retourner le premier front de la population finale
|
|
93
|
-
$finalFronts = self::nonDominatedSort($population);
|
|
94
|
-
$bestFront = $finalFronts[0] ?? [];
|
|
95
|
-
|
|
96
|
-
// Filtrer pour ne garder que les solutions avec des objectifs uniques
|
|
97
|
-
$uniqueSolutionsMap = [];
|
|
98
|
-
foreach ($bestFront as $p) {
|
|
99
|
-
$key = json_encode($p['objectives']);
|
|
100
|
-
if (!isset($uniqueSolutionsMap[$key])) {
|
|
101
|
-
$uniqueSolutionsMap[$key] = [
|
|
102
|
-
'solution' => $p['individual'],
|
|
103
|
-
'objectives' => $p['objectives'],
|
|
104
|
-
];
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
return array_values($uniqueSolutionsMap);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Détermine si la solution A domine la solution B.
|
|
112
|
-
* @param array<float> $objectivesA
|
|
113
|
-
* @param array<float> $objectivesB
|
|
114
|
-
*/
|
|
115
|
-
private static function paretoDominates(array $objectivesA, array $objectivesB): bool
|
|
116
|
-
{
|
|
117
|
-
$aIsBetterInOne = false;
|
|
118
|
-
for ($i = 0; $i < count($objectivesA); $i++) {
|
|
119
|
-
if ($objectivesA[$i] > $objectivesB[$i]) {
|
|
120
|
-
return false; // A est pire sur au moins un objectif
|
|
121
|
-
}
|
|
122
|
-
if ($objectivesA[$i] < $objectivesB[$i]) {
|
|
123
|
-
$aIsBetterInOne = true; // A est strictement meilleur sur au moins un
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return $aIsBetterInOne;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Trie une population en fronts de Pareto non-dominés.
|
|
131
|
-
* @param array<int, array> &$populationWithObjectives
|
|
132
|
-
* @return array<int, array>
|
|
133
|
-
*/
|
|
134
|
-
private static function nonDominatedSort(array &$populationWithObjectives): array
|
|
135
|
-
{
|
|
136
|
-
$fronts = [[]];
|
|
137
|
-
$n = count($populationWithObjectives);
|
|
138
|
-
|
|
139
|
-
for ($i = 0; $i < $n; $i++) {
|
|
140
|
-
$p1 = &$populationWithObjectives[$i];
|
|
141
|
-
$p1['dominationCount'] = 0;
|
|
142
|
-
$p1['dominatedSolutions'] = [];
|
|
143
|
-
|
|
144
|
-
for ($j = 0; $j < $n; $j++) {
|
|
145
|
-
if ($i === $j) continue;
|
|
146
|
-
$p2 = &$populationWithObjectives[$j];
|
|
147
|
-
|
|
148
|
-
if (self::paretoDominates($p1['objectives'], $p2['objectives'])) {
|
|
149
|
-
$p1['dominatedSolutions'][] = $j;
|
|
150
|
-
} elseif (self::paretoDominates($p2['objectives'], $p1['objectives'])) {
|
|
151
|
-
$p1['dominationCount']++;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if ($p1['dominationCount'] === 0) {
|
|
156
|
-
$p1['rank'] = 0;
|
|
157
|
-
$fronts[0][] = $p1;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
$i = 0;
|
|
162
|
-
while (!empty($fronts[$i])) {
|
|
163
|
-
$nextFront = [];
|
|
164
|
-
foreach ($fronts[$i] as $p1) {
|
|
165
|
-
foreach ($p1['dominatedSolutions'] as $p2_idx) {
|
|
166
|
-
$p2 = &$populationWithObjectives[$p2_idx];
|
|
167
|
-
$p2['dominationCount']--;
|
|
168
|
-
if ($p2['dominationCount'] === 0) {
|
|
169
|
-
$p2['rank'] = $i + 1;
|
|
170
|
-
$nextFront[] = $p2;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
$i++;
|
|
175
|
-
if (!empty($nextFront)) {
|
|
176
|
-
$fronts[$i] = $nextFront;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
return $fronts;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Calcule la distance de promiscuité (crowding distance) pour un front.
|
|
184
|
-
* @param array<int, array> &$front
|
|
185
|
-
*/
|
|
186
|
-
private static function calculateCrowdingDistance(array &$front): void
|
|
187
|
-
{
|
|
188
|
-
if (empty($front)) return;
|
|
189
|
-
|
|
190
|
-
$numObjectives = count($front[0]['objectives']);
|
|
191
|
-
$l = count($front);
|
|
192
|
-
|
|
193
|
-
foreach ($front as &$p) {
|
|
194
|
-
$p['crowdingDistance'] = 0;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
for ($i = 0; $i < $numObjectives; $i++) {
|
|
198
|
-
// Trier le front par l'objectif courant
|
|
199
|
-
usort($front, fn ($a, $b) => $a['objectives'][$i] <=> $b['objectives'][$i]);
|
|
200
|
-
|
|
201
|
-
$minObj = $front[0]['objectives'][$i];
|
|
202
|
-
$maxObj = $front[$l - 1]['objectives'][$i];
|
|
203
|
-
|
|
204
|
-
// Les solutions aux extrémités ont une distance infinie
|
|
205
|
-
$front[0]['crowdingDistance'] = INF;
|
|
206
|
-
$front[$l - 1]['crowdingDistance'] = INF;
|
|
207
|
-
|
|
208
|
-
if ($maxObj === $minObj) continue;
|
|
209
|
-
|
|
210
|
-
for ($j = 1; $j < $l - 1; $j++) {
|
|
211
|
-
$front[$j]['crowdingDistance'] +=
|
|
212
|
-
($front[$j + 1]['objectives'][$i] - $front[$j - 1]['objectives'][$i]) /
|
|
213
|
-
($maxObj - $minObj);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Calcule la déviation d'une série de chiffres par rapport à la loi de Benford.
|
|
220
|
-
* @public
|
|
221
|
-
* @param array<int|float> $numbers
|
|
222
|
-
*/
|
|
223
|
-
public static function benfordTest(array $numbers): float // Rendre la méthode publique et statique
|
|
224
|
-
{
|
|
225
|
-
if (count($numbers) < 10) {
|
|
226
|
-
return 0.0; // Pas assez de données
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
$leadingDigits = array_map(function ($n) {
|
|
230
|
-
$str = ltrim((string)$n, '0.');
|
|
231
|
-
return $str[0] ?? '';
|
|
232
|
-
}, $numbers);
|
|
233
|
-
|
|
234
|
-
$leadingDigits = array_filter($leadingDigits, fn ($d) => $d >= '1' && $d <= '9');
|
|
235
|
-
|
|
236
|
-
if (count($leadingDigits) < 10) {
|
|
237
|
-
return 0.0;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
$counts = array_fill(1, 9, 0);
|
|
241
|
-
foreach ($leadingDigits as $digit) {
|
|
242
|
-
$counts[(int)$digit]++;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
$benfordDistribution = [1 => 30.1, 2 => 17.6, 3 => 12.5, 4 => 9.7, 5 => 7.9, 6 => 6.7, 7 => 5.8, 8 => 5.1, 9 => 4.6];
|
|
246
|
-
|
|
247
|
-
$totalDeviation = 0.0;
|
|
248
|
-
for ($i = 1; $i <= 9; $i++) {
|
|
249
|
-
$observedFrequency = ($counts[$i] / count($leadingDigits)) * 100;
|
|
250
|
-
$expectedFrequency = $benfordDistribution[$i];
|
|
251
|
-
$totalDeviation += pow($observedFrequency - $expectedFrequency, 2);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
return sqrt($totalDeviation) / 50.0;
|
|
255
|
-
}
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Optimization;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Bibliothèque d'algorithmes d'optimisation.
|
|
9
|
+
*/
|
|
10
|
+
class Optimization
|
|
11
|
+
{
|
|
12
|
+
/**
|
|
13
|
+
* Génère un nombre flottant aléatoire cryptographiquement sûr entre 0 (inclus) et 1 (exclus).
|
|
14
|
+
*/
|
|
15
|
+
private static function secureRandom(): float
|
|
16
|
+
{
|
|
17
|
+
try {
|
|
18
|
+
return random_int(0, PHP_INT_MAX - 1) / PHP_INT_MAX;
|
|
19
|
+
} catch (\Exception $e) {
|
|
20
|
+
return (float)mt_rand() / (float)mt_getrandmax(); // Fallback
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Algorithme génétique multi-objectifs (inspiré de NSGA-II).
|
|
26
|
+
* @param callable $createIndividual
|
|
27
|
+
* @param callable $fitnessFunction
|
|
28
|
+
* @param callable $crossover
|
|
29
|
+
* @param callable $mutate
|
|
30
|
+
* @param array $options
|
|
31
|
+
* @return array<int, array{solution: mixed, objectives: array<float>}>
|
|
32
|
+
*/
|
|
33
|
+
public static function geneticAlgorithmMultiObjective(
|
|
34
|
+
callable $createIndividual,
|
|
35
|
+
callable $fitnessFunction,
|
|
36
|
+
callable $crossover,
|
|
37
|
+
callable $mutate,
|
|
38
|
+
array $options = []
|
|
39
|
+
): array {
|
|
40
|
+
$generations = $options['generations'] ?? 150;
|
|
41
|
+
$populationSize = $options['populationSize'] ?? 60;
|
|
42
|
+
$mutationRate = $options['mutationRate'] ?? 0.1;
|
|
43
|
+
|
|
44
|
+
$population = [];
|
|
45
|
+
for ($i = 0; $i < $populationSize; $i++) {
|
|
46
|
+
$individual = $createIndividual();
|
|
47
|
+
$population[] = [
|
|
48
|
+
'individual' => $individual,
|
|
49
|
+
'objectives' => $fitnessFunction($individual)
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for ($gen = 0; $gen < $generations; $gen++) {
|
|
54
|
+
// 1. Créer une population d'enfants
|
|
55
|
+
$offspring = [];
|
|
56
|
+
for ($i = 0; $i < $populationSize; $i++) {
|
|
57
|
+
$parent1 = $population[random_int(0, count($population) - 1)];
|
|
58
|
+
$parent2 = $population[random_int(0, count($population) - 1)];
|
|
59
|
+
$childIndividual = $crossover($parent1['individual'], $parent2['individual']);
|
|
60
|
+
if (self::secureRandom() < $mutationRate) {
|
|
61
|
+
$childIndividual = $mutate($childIndividual);
|
|
62
|
+
}
|
|
63
|
+
$offspring[] = [
|
|
64
|
+
'individual' => $childIndividual,
|
|
65
|
+
'objectives' => $fitnessFunction($childIndividual)
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 2. Combiner parents et enfants
|
|
70
|
+
$combinedPopulation = array_merge($population, $offspring);
|
|
71
|
+
|
|
72
|
+
// 3. Trier la population combinée en fronts
|
|
73
|
+
$fronts = self::nonDominatedSort($combinedPopulation);
|
|
74
|
+
|
|
75
|
+
// 4. Construire la nouvelle population
|
|
76
|
+
$newPopulation = [];
|
|
77
|
+
foreach ($fronts as $front) {
|
|
78
|
+
if (count($newPopulation) + count($front) <= $populationSize) {
|
|
79
|
+
$newPopulation = array_merge($newPopulation, $front);
|
|
80
|
+
} else {
|
|
81
|
+
self::calculateCrowdingDistance($front);
|
|
82
|
+
// Trier par distance décroissante
|
|
83
|
+
usort($front, fn ($a, $b) => $b['crowdingDistance'] <=> $a['crowdingDistance']);
|
|
84
|
+
$remaining = $populationSize - count($newPopulation);
|
|
85
|
+
$newPopulation = array_merge($newPopulation, array_slice($front, 0, $remaining));
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
$population = $newPopulation;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Retourner le premier front de la population finale
|
|
93
|
+
$finalFronts = self::nonDominatedSort($population);
|
|
94
|
+
$bestFront = $finalFronts[0] ?? [];
|
|
95
|
+
|
|
96
|
+
// Filtrer pour ne garder que les solutions avec des objectifs uniques
|
|
97
|
+
$uniqueSolutionsMap = [];
|
|
98
|
+
foreach ($bestFront as $p) {
|
|
99
|
+
$key = json_encode($p['objectives']);
|
|
100
|
+
if (!isset($uniqueSolutionsMap[$key])) {
|
|
101
|
+
$uniqueSolutionsMap[$key] = [
|
|
102
|
+
'solution' => $p['individual'],
|
|
103
|
+
'objectives' => $p['objectives'],
|
|
104
|
+
];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return array_values($uniqueSolutionsMap);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Détermine si la solution A domine la solution B.
|
|
112
|
+
* @param array<float> $objectivesA
|
|
113
|
+
* @param array<float> $objectivesB
|
|
114
|
+
*/
|
|
115
|
+
private static function paretoDominates(array $objectivesA, array $objectivesB): bool
|
|
116
|
+
{
|
|
117
|
+
$aIsBetterInOne = false;
|
|
118
|
+
for ($i = 0; $i < count($objectivesA); $i++) {
|
|
119
|
+
if ($objectivesA[$i] > $objectivesB[$i]) {
|
|
120
|
+
return false; // A est pire sur au moins un objectif
|
|
121
|
+
}
|
|
122
|
+
if ($objectivesA[$i] < $objectivesB[$i]) {
|
|
123
|
+
$aIsBetterInOne = true; // A est strictement meilleur sur au moins un
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return $aIsBetterInOne;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Trie une population en fronts de Pareto non-dominés.
|
|
131
|
+
* @param array<int, array> &$populationWithObjectives
|
|
132
|
+
* @return array<int, array>
|
|
133
|
+
*/
|
|
134
|
+
private static function nonDominatedSort(array &$populationWithObjectives): array
|
|
135
|
+
{
|
|
136
|
+
$fronts = [[]];
|
|
137
|
+
$n = count($populationWithObjectives);
|
|
138
|
+
|
|
139
|
+
for ($i = 0; $i < $n; $i++) {
|
|
140
|
+
$p1 = &$populationWithObjectives[$i];
|
|
141
|
+
$p1['dominationCount'] = 0;
|
|
142
|
+
$p1['dominatedSolutions'] = [];
|
|
143
|
+
|
|
144
|
+
for ($j = 0; $j < $n; $j++) {
|
|
145
|
+
if ($i === $j) continue;
|
|
146
|
+
$p2 = &$populationWithObjectives[$j];
|
|
147
|
+
|
|
148
|
+
if (self::paretoDominates($p1['objectives'], $p2['objectives'])) {
|
|
149
|
+
$p1['dominatedSolutions'][] = $j;
|
|
150
|
+
} elseif (self::paretoDominates($p2['objectives'], $p1['objectives'])) {
|
|
151
|
+
$p1['dominationCount']++;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if ($p1['dominationCount'] === 0) {
|
|
156
|
+
$p1['rank'] = 0;
|
|
157
|
+
$fronts[0][] = $p1;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
$i = 0;
|
|
162
|
+
while (!empty($fronts[$i])) {
|
|
163
|
+
$nextFront = [];
|
|
164
|
+
foreach ($fronts[$i] as $p1) {
|
|
165
|
+
foreach ($p1['dominatedSolutions'] as $p2_idx) {
|
|
166
|
+
$p2 = &$populationWithObjectives[$p2_idx];
|
|
167
|
+
$p2['dominationCount']--;
|
|
168
|
+
if ($p2['dominationCount'] === 0) {
|
|
169
|
+
$p2['rank'] = $i + 1;
|
|
170
|
+
$nextFront[] = $p2;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
$i++;
|
|
175
|
+
if (!empty($nextFront)) {
|
|
176
|
+
$fronts[$i] = $nextFront;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return $fronts;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Calcule la distance de promiscuité (crowding distance) pour un front.
|
|
184
|
+
* @param array<int, array> &$front
|
|
185
|
+
*/
|
|
186
|
+
private static function calculateCrowdingDistance(array &$front): void
|
|
187
|
+
{
|
|
188
|
+
if (empty($front)) return;
|
|
189
|
+
|
|
190
|
+
$numObjectives = count($front[0]['objectives']);
|
|
191
|
+
$l = count($front);
|
|
192
|
+
|
|
193
|
+
foreach ($front as &$p) {
|
|
194
|
+
$p['crowdingDistance'] = 0;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
for ($i = 0; $i < $numObjectives; $i++) {
|
|
198
|
+
// Trier le front par l'objectif courant
|
|
199
|
+
usort($front, fn ($a, $b) => $a['objectives'][$i] <=> $b['objectives'][$i]);
|
|
200
|
+
|
|
201
|
+
$minObj = $front[0]['objectives'][$i];
|
|
202
|
+
$maxObj = $front[$l - 1]['objectives'][$i];
|
|
203
|
+
|
|
204
|
+
// Les solutions aux extrémités ont une distance infinie
|
|
205
|
+
$front[0]['crowdingDistance'] = INF;
|
|
206
|
+
$front[$l - 1]['crowdingDistance'] = INF;
|
|
207
|
+
|
|
208
|
+
if ($maxObj === $minObj) continue;
|
|
209
|
+
|
|
210
|
+
for ($j = 1; $j < $l - 1; $j++) {
|
|
211
|
+
$front[$j]['crowdingDistance'] +=
|
|
212
|
+
($front[$j + 1]['objectives'][$i] - $front[$j - 1]['objectives'][$i]) /
|
|
213
|
+
($maxObj - $minObj);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Calcule la déviation d'une série de chiffres par rapport à la loi de Benford.
|
|
220
|
+
* @public
|
|
221
|
+
* @param array<int|float> $numbers
|
|
222
|
+
*/
|
|
223
|
+
public static function benfordTest(array $numbers): float // Rendre la méthode publique et statique
|
|
224
|
+
{
|
|
225
|
+
if (count($numbers) < 10) {
|
|
226
|
+
return 0.0; // Pas assez de données
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
$leadingDigits = array_map(function ($n) {
|
|
230
|
+
$str = ltrim((string)$n, '0.');
|
|
231
|
+
return $str[0] ?? '';
|
|
232
|
+
}, $numbers);
|
|
233
|
+
|
|
234
|
+
$leadingDigits = array_filter($leadingDigits, fn ($d) => $d >= '1' && $d <= '9');
|
|
235
|
+
|
|
236
|
+
if (count($leadingDigits) < 10) {
|
|
237
|
+
return 0.0;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
$counts = array_fill(1, 9, 0);
|
|
241
|
+
foreach ($leadingDigits as $digit) {
|
|
242
|
+
$counts[(int)$digit]++;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
$benfordDistribution = [1 => 30.1, 2 => 17.6, 3 => 12.5, 4 => 9.7, 5 => 7.9, 6 => 6.7, 7 => 5.8, 8 => 5.1, 9 => 4.6];
|
|
246
|
+
|
|
247
|
+
$totalDeviation = 0.0;
|
|
248
|
+
for ($i = 1; $i <= 9; $i++) {
|
|
249
|
+
$observedFrequency = ($counts[$i] / count($leadingDigits)) * 100;
|
|
250
|
+
$expectedFrequency = $benfordDistribution[$i];
|
|
251
|
+
$totalDeviation += pow($observedFrequency - $expectedFrequency, 2);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return sqrt($totalDeviation) / 50.0;
|
|
255
|
+
}
|
|
256
256
|
}
|