@anonympins/fingerprint 0.4.2 → 0.4.3

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.
@@ -1,305 +1,402 @@
1
- <?php
2
-
3
- declare(strict_types=1);
4
-
5
- namespace Anonympins\Fingerprint\Optimization;
6
-
7
- /**
8
- * Opérateurs pour les problèmes d'optimisation.
9
- */
10
- class OptimizationOperators
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
- * Crée un évaluateur pour l'optimisation de portefeuille.
26
- * @param array $config
27
- * @return callable
28
- */
29
- public static function createPortfolioAllocator(array $config): callable
30
- {
31
- // Cette fonction est un placeholder. Une implémentation complète nécessiterait
32
- // une logique de calcul de rendement et de volatilité de portefeuille.
33
- return function (array $weights) use ($config): float {
34
- // Minimiser le rendement négatif (donc maximiser le rendement)
35
- return -array_sum($weights);
36
- };
37
- }
38
-
39
- /**
40
- * Crée un évaluateur pour l'auto-tuning complet de la configuration de sécurité.
41
- * @param array $context
42
- * @return callable
43
- */
44
- public static function createFullSecurityConfigEvaluator(array $context): callable
45
- {
46
- $trafficData = $context['trafficData'];
47
-
48
- return function (array $config) use ($trafficData): array {
49
- $falsePositives = 0;
50
- $falseNegatives = 0;
51
- $totalHumans = 0;
52
- $totalBots = 0;
53
-
54
- $calculateScore = function (array $log) use ($config): float {
55
- $score = 0.0;
56
- foreach ($config['weights'] as $key => $weight) {
57
- $score += ($log['vector'][$key] ?? 0) * $weight;
58
- }
59
- return $score;
60
- };
61
-
62
- $confidenceWeights = [
63
- 'request_passed' => 0.7,
64
- 'challenge_issued' => 1.0,
65
- 'request_blocked' => 1.0,
66
- 'challenge_solved' => 1.5,
67
- 'trap_triggered' => 2.0,
68
- ];
69
-
70
- foreach ($trafficData as $log) {
71
- $confidence = $confidenceWeights[$log['type']] ?? 1.0;
72
- $isLikelyBot = in_array($log['type'], ['challenge_issued', 'request_blocked', 'trap_triggered']);
73
- $isLikelyHuman = in_array($log['type'], ['request_passed', 'challenge_solved']);
74
-
75
- if ($isLikelyBot) {
76
- $totalBots += $confidence;
77
- $score = $calculateScore($log);
78
- if ($score < $config['thresholds']['low']) {
79
- $falseNegatives += $confidence;
80
- }
81
- } elseif ($isLikelyHuman) {
82
- $totalHumans += $confidence;
83
- $score = $calculateScore($log);
84
- if ($score >= $config['thresholds']['low']) {
85
- $falsePositives += $confidence;
86
- }
87
- }
88
- }
89
-
90
- $falsePositiveRate = $totalHumans > 0 ? $falsePositives / $totalHumans : 0;
91
- $falseNegativeRate = $totalBots > 0 ? $falseNegatives / $totalBots : 0;
92
-
93
- return [$falsePositiveRate, $falseNegativeRate];
94
- };
95
- }
96
-
97
- /**
98
- * Résout le problème de l'auto-tuning complet de la configuration de sécurité.
99
- * @param array $context
100
- * @param array $options
101
- * @return array
102
- */
103
- public static function solveFullSecurityTuning(array $context, array $options = []): array
104
- {
105
- $fitnessFunction = self::createFullSecurityConfigEvaluator($context);
106
-
107
- $createIndividual = function (): array {
108
- return [
109
- 'thresholds' => [
110
- 'low' => 15 + self::secureRandom() * 20,
111
- 'medium' => 40 + self::secureRandom() * 25,
112
- 'high' => 70 + self::secureRandom() * 20,
113
- ],
114
- 'weights' => [
115
- 'historyScore' => self::secureRandom(),
116
- 'rotationScore' => self::secureRandom(),
117
- 'headerAnomalyScore' => self::secureRandom(),
118
- 'requestPatternScore' => 0.5 + self::secureRandom(),
119
- 'inconsistencyScore' => self::secureRandom(),
120
- 'honeypotScore' => 1.0,
121
- 'behaviorScore' => self::secureRandom(),
122
- 'crossLayerInconsistencyScore' => self::secureRandom(),
123
- 'timeInconsistencyScore' => self::secureRandom(),
124
- 'tlsSpoofingScore' => self::secureRandom(),
125
- 'botScore' => self::secureRandom(),
126
- 'cookieDroppingScore' => self::secureRandom(),
127
- 'threatIntelScore' => self::secureRandom(),
128
- ],
129
- 'patterns' => [
130
- 'velocityThreshold' => 100 + self::secureRandom() * 400,
131
- 'burstThreshold' => 300 + self::secureRandom() * 700,
132
- 'scrapeThreshold' => 500 + self::secureRandom() * 1000,
133
- 'regularityThreshold' => 50 + self::secureRandom() * 200,
134
- 'decayFactor' => 0.85 + self::secureRandom() * 0.14,
135
- 'inactivityReset' => 15000 + self::secureRandom() * 45000,
136
- ]
137
- ];
138
- };
139
-
140
- $crossover = function (array $c1, array $c2): array {
141
- $child = $c1;
142
- foreach (['thresholds', 'weights', 'patterns'] as $section) {
143
- foreach ($child[$section] as $key => $value) {
144
- if ($key !== 'honeypotScore') {
145
- $child[$section][$key] = ($c1[$section][$key] + $c2[$section][$key]) / 2;
146
- }
147
- }
148
- }
149
- return $child;
150
- };
151
-
152
- $mutate = function (array $c): array {
153
- $newConfig = $c;
154
- $sections = [
155
- ['name' => 'patterns', 'weight' => 0.5],
156
- ['name' => 'weights', 'weight' => 0.35],
157
- ['name' => 'thresholds', 'weight' => 0.15]
158
- ];
159
- $rand = self::secureRandom();
160
- $cumulativeWeight = 0;
161
- $sectionToMutate = 'patterns';
162
- foreach ($sections as $section) {
163
- $cumulativeWeight += $section['weight'];
164
- if ($rand < $cumulativeWeight) {
165
- $sectionToMutate = $section['name'];
166
- break;
167
- }
168
- }
169
-
170
- $keys = array_keys($newConfig[$sectionToMutate]);
171
- $keyToMutate = $keys[random_int(0, count($keys) - 1)];
172
-
173
- if ($keyToMutate === 'honeypotScore') return $newConfig;
174
-
175
- $mutationAmount = (self::secureRandom() - 0.5) * 0.4;
176
- $newConfig[$sectionToMutate][$keyToMutate] *= (1 + $mutationAmount);
177
-
178
- if ($sectionToMutate === 'weights') {
179
- $newConfig[$sectionToMutate][$keyToMutate] = max(0, min(1.5, $newConfig[$sectionToMutate][$keyToMutate]));
180
- }
181
-
182
- return $newConfig;
183
- };
184
-
185
- return Optimization::geneticAlgorithmMultiObjective(
186
- $createIndividual,
187
- $fitnessFunction,
188
- $crossover,
189
- $mutate,
190
- array_merge(['generations' => 50, 'populationSize' => 50], $options)
191
- );
192
- }
193
-
194
- /**
195
- * Crée un évaluateur pour trouver les seuils de détection de fraude optimaux.
196
- * @param array $context
197
- * @return callable
198
- */
199
- public static function createFraudThresholdEvaluator(array $context): callable
200
- {
201
- $legitimateClicks = $context['legitimateClicks'] ?? [];
202
- $fraudulentClicks = $context['fraudulentClicks'] ?? [];
203
-
204
- return function (array $solution) use ($legitimateClicks, $fraudulentClicks): array {
205
- [$minTimeToClick, $maxClickVariance, $minMouseEntropy, $minScrollEvents] = $solution;
206
-
207
- $truePositives = 0;
208
- $falsePositives = 0;
209
-
210
- foreach ($fraudulentClicks as $click) {
211
- if (
212
- ($click['timeToClick'] < $minTimeToClick) ||
213
- ($click['mouseEntropy'] < $minMouseEntropy)
214
- ) {
215
- $truePositives++;
216
- }
217
- }
218
-
219
- foreach ($legitimateClicks as $click) {
220
- if (
221
- ($click['timeToClick'] < $minTimeToClick) ||
222
- ($click['mouseEntropy'] < $minMouseEntropy)
223
- ) {
224
- $falsePositives++;
225
- }
226
- }
227
-
228
- $totalFraudulent = count($fraudulentClicks) ?: 1;
229
- $totalLegitimate = count($legitimateClicks) ?: 1;
230
-
231
- $objective1 = 1 - ($truePositives / $totalFraudulent);
232
- $objective2 = $falsePositives / $totalLegitimate;
233
-
234
- return [$objective1, $objective2];
235
- };
236
- }
237
-
238
- /**
239
- * Résout le problème de la détection de fraude.
240
- * @param array $context
241
- * @param array $options
242
- * @return array
243
- */
244
- public static function solveFraudDetection(array $context, array $options = []): array
245
- {
246
- $fitnessFunction = self::createFraudThresholdEvaluator($context);
247
-
248
- $createIndividual = function (): array {
249
- return [
250
- 100 + self::secureRandom() * 4900, // minTimeToClick
251
- 1 + self::secureRandom() * 9999, // maxClickVariance
252
- self::secureRandom() * 0.5, // minMouseEntropy
253
- floor(self::secureRandom() * 10) // minScrollEvents
254
- ];
255
- };
256
-
257
- $crossover = fn ($s1, $s2) => array_map(fn ($a, $b) => ($a + $b) / 2, $s1, $s2);
258
-
259
- $mutate = function (array $solution): array {
260
- $i = random_int(0, 3);
261
- $mutationFactors = [500, 1000, 0.1, 2];
262
- $solution[$i] += (self::secureRandom() - 0.5) * $mutationFactors[$i];
263
- return $solution;
264
- };
265
-
266
- return Optimization::geneticAlgorithmMultiObjective(
267
- $createIndividual,
268
- $fitnessFunction,
269
- $crossover,
270
- $mutate,
271
- array_merge(['generations' => 80, 'populationSize' => 60], $options)
272
- );
273
- }
274
-
275
- /**
276
- * Placeholder pour le solveur TSP.
277
- * @param array $cities
278
- * @param array $options
279
- * @return array
280
- */
281
- public static function solveTSP(array $cities, array $options = []): array
282
- {
283
- // Implémentation factice pour la complétude
284
- return ['solution' => array_keys($cities), 'energy' => 100];
285
- }
286
-
287
- /**
288
- * Placeholder pour le solveur de portefeuille.
289
- * @param array $assets
290
- * @param float $maxVolatility
291
- * @param array $options
292
- * @return array
293
- */
294
- public static function solvePortfolio(array $assets, float $maxVolatility, array $options = []): array
295
- {
296
- // Implémentation factice pour la complétude
297
- return ['solution' => array_fill(0, count($assets), 1 / count($assets)), 'fitness' => -0.1];
298
- }
299
-
300
- public static function solveFacilityLocation(array $customers, int $numFacilities, array $bounds, array $options = []): array
301
- {
302
- // Implémentation factice pour la complétude
303
- return ['solution' => [], 'energy' => 0];
304
- }
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Anonympins\Fingerprint\Optimization;
6
+
7
+ /**
8
+ * Opérateurs pour les problèmes d'optimisation.
9
+ */
10
+ class OptimizationOperators
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
+ * Crée un évaluateur pour l'optimisation de portefeuille.
26
+ * @param array $config
27
+ * @return callable
28
+ */
29
+ public static function createPortfolioAllocator(array $config): callable
30
+ {
31
+ // Cette fonction est un placeholder. Une implémentation complète nécessiterait
32
+ // une logique de calcul de rendement et de volatilité de portefeuille.
33
+ return function (array $weights) use ($config): float {
34
+ // Minimiser le rendement négatif (donc maximiser le rendement)
35
+ return -array_sum($weights);
36
+ };
37
+ }
38
+
39
+ /**
40
+ * Crée un évaluateur pour l'auto-tuning complet de la configuration de sécurité.
41
+ * @param array $context
42
+ * @return callable
43
+ */
44
+ public static function createFullSecurityConfigEvaluator(array $context): callable
45
+ {
46
+ $trafficData = $context['trafficData'];
47
+
48
+ return function (array $config) use ($trafficData): array {
49
+ $falsePositives = 0;
50
+ $falseNegatives = 0;
51
+ $totalHumans = 0;
52
+ $totalBots = 0;
53
+
54
+ $calculateScore = function (array $log) use ($config): float {
55
+ $score = 0.0;
56
+ foreach ($config['weights'] as $key => $weight) {
57
+ $score += ($log['vector'][$key] ?? 0) * $weight;
58
+ }
59
+ return $score;
60
+ };
61
+
62
+ $confidenceWeights = [
63
+ 'request_passed' => 0.7,
64
+ 'challenge_issued' => 1.0,
65
+ 'request_blocked' => 1.0,
66
+ 'challenge_solved' => 1.5,
67
+ 'trap_triggered' => 2.0,
68
+ ];
69
+
70
+ foreach ($trafficData as $log) {
71
+ $confidence = $confidenceWeights[$log['type']] ?? 1.0;
72
+ $isLikelyBot = in_array($log['type'], ['challenge_issued', 'request_blocked', 'trap_triggered']);
73
+ $isLikelyHuman = in_array($log['type'], ['request_passed', 'challenge_solved']);
74
+
75
+ if ($isLikelyBot) {
76
+ $totalBots += $confidence;
77
+ $score = $calculateScore($log);
78
+ if ($score < $config['thresholds']['low']) {
79
+ $falseNegatives += $confidence;
80
+ }
81
+ } elseif ($isLikelyHuman) {
82
+ $totalHumans += $confidence;
83
+ $score = $calculateScore($log);
84
+ if ($score >= $config['thresholds']['low']) {
85
+ $falsePositives += $confidence;
86
+ }
87
+ }
88
+ }
89
+
90
+ $falsePositiveRate = $totalHumans > 0 ? $falsePositives / $totalHumans : 0;
91
+ $falseNegativeRate = $totalBots > 0 ? $falseNegatives / $totalBots : 0;
92
+
93
+ return [$falsePositiveRate, $falseNegativeRate];
94
+ };
95
+ }
96
+
97
+ /**
98
+ * Résout le problème de l'auto-tuning complet de la configuration de sécurité.
99
+ * @param array $context
100
+ * @param array $options
101
+ * @return array
102
+ */
103
+ public static function solveFullSecurityTuning(array $context, array $options = []): array
104
+ {
105
+ $fitnessFunction = self::createFullSecurityConfigEvaluator($context);
106
+
107
+ $createIndividual = function (): array {
108
+ return [
109
+ 'thresholds' => [
110
+ 'low' => 15 + self::secureRandom() * 20,
111
+ 'medium' => 40 + self::secureRandom() * 25,
112
+ 'high' => 70 + self::secureRandom() * 20,
113
+ ],
114
+ 'weights' => [
115
+ 'historyScore' => self::secureRandom(),
116
+ 'rotationScore' => self::secureRandom(),
117
+ 'headerAnomalyScore' => self::secureRandom(),
118
+ 'requestPatternScore' => 0.5 + self::secureRandom(),
119
+ 'inconsistencyScore' => self::secureRandom(),
120
+ 'honeypotScore' => 1.0,
121
+ 'behaviorScore' => self::secureRandom(),
122
+ 'crossLayerInconsistencyScore' => self::secureRandom(),
123
+ 'timeInconsistencyScore' => self::secureRandom(),
124
+ 'tlsSpoofingScore' => self::secureRandom(),
125
+ 'botScore' => self::secureRandom(),
126
+ 'cookieDroppingScore' => self::secureRandom(),
127
+ 'threatIntelScore' => self::secureRandom(),
128
+ ],
129
+ 'patterns' => [
130
+ 'velocityThreshold' => 100 + self::secureRandom() * 400,
131
+ 'burstThreshold' => 300 + self::secureRandom() * 700,
132
+ 'scrapeThreshold' => 500 + self::secureRandom() * 1000,
133
+ 'regularityThreshold' => 50 + self::secureRandom() * 200,
134
+ 'decayFactor' => 0.85 + self::secureRandom() * 0.14,
135
+ 'inactivityReset' => 15000 + self::secureRandom() * 45000,
136
+ ]
137
+ ];
138
+ };
139
+
140
+ $crossover = function (array $c1, array $c2): array {
141
+ $child = $c1;
142
+ foreach (['thresholds', 'weights', 'patterns'] as $section) {
143
+ foreach ($child[$section] as $key => $value) {
144
+ if ($key !== 'honeypotScore') {
145
+ $child[$section][$key] = ($c1[$section][$key] + $c2[$section][$key]) / 2;
146
+ }
147
+ }
148
+ }
149
+ return $child;
150
+ };
151
+
152
+ $mutate = function (array $c): array {
153
+ $newConfig = $c;
154
+ $sections = [
155
+ ['name' => 'patterns', 'weight' => 0.5],
156
+ ['name' => 'weights', 'weight' => 0.35],
157
+ ['name' => 'thresholds', 'weight' => 0.15]
158
+ ];
159
+ $rand = self::secureRandom();
160
+ $cumulativeWeight = 0;
161
+ $sectionToMutate = 'patterns';
162
+ foreach ($sections as $section) {
163
+ $cumulativeWeight += $section['weight'];
164
+ if ($rand < $cumulativeWeight) {
165
+ $sectionToMutate = $section['name'];
166
+ break;
167
+ }
168
+ }
169
+
170
+ $keys = array_keys($newConfig[$sectionToMutate]);
171
+ $keyToMutate = $keys[random_int(0, count($keys) - 1)];
172
+
173
+ if ($keyToMutate === 'honeypotScore') return $newConfig;
174
+
175
+ $mutationAmount = (self::secureRandom() - 0.5) * 0.4;
176
+ $newConfig[$sectionToMutate][$keyToMutate] *= (1 + $mutationAmount);
177
+
178
+ if ($sectionToMutate === 'weights') {
179
+ $newConfig[$sectionToMutate][$keyToMutate] = max(0, min(1.5, $newConfig[$sectionToMutate][$keyToMutate]));
180
+ }
181
+
182
+ return $newConfig;
183
+ };
184
+
185
+ return Optimization::geneticAlgorithmMultiObjective(
186
+ $createIndividual,
187
+ $fitnessFunction,
188
+ $crossover,
189
+ $mutate,
190
+ array_merge(['generations' => 50, 'populationSize' => 50], $options)
191
+ );
192
+ }
193
+
194
+ /**
195
+ * Crée un évaluateur pour trouver les seuils de détection de fraude optimaux.
196
+ * @param array $context
197
+ * @return callable
198
+ */
199
+ public static function createFraudThresholdEvaluator(array $context): callable
200
+ {
201
+ $legitimateClicks = $context['legitimateClicks'] ?? [];
202
+ $fraudulentClicks = $context['fraudulentClicks'] ?? [];
203
+
204
+ return function (array $solution) use ($legitimateClicks, $fraudulentClicks): array {
205
+ [$minTimeToClick, $maxClickVariance, $minMouseEntropy, $minScrollEvents] = $solution;
206
+
207
+ $truePositives = 0;
208
+ $falsePositives = 0;
209
+
210
+ foreach ($fraudulentClicks as $click) {
211
+ if (
212
+ ($click['timeToClick'] < $minTimeToClick) ||
213
+ ($click['mouseEntropy'] < $minMouseEntropy)
214
+ ) {
215
+ $truePositives++;
216
+ }
217
+ }
218
+
219
+ foreach ($legitimateClicks as $click) {
220
+ if (
221
+ ($click['timeToClick'] < $minTimeToClick) ||
222
+ ($click['mouseEntropy'] < $minMouseEntropy)
223
+ ) {
224
+ $falsePositives++;
225
+ }
226
+ }
227
+
228
+ $totalFraudulent = count($fraudulentClicks) ?: 1;
229
+ $totalLegitimate = count($legitimateClicks) ?: 1;
230
+
231
+ $objective1 = 1 - ($truePositives / $totalFraudulent);
232
+ $objective2 = $falsePositives / $totalLegitimate;
233
+
234
+ return [$objective1, $objective2];
235
+ };
236
+ }
237
+
238
+ /**
239
+ * Résout le problème de la détection de fraude.
240
+ * @param array $context
241
+ * @param array $options
242
+ * @return array
243
+ */
244
+ public static function solveFraudDetection(array $context, array $options = []): array
245
+ {
246
+ $fitnessFunction = self::createFraudThresholdEvaluator($context);
247
+
248
+ $createIndividual = function (): array {
249
+ return [
250
+ 100 + self::secureRandom() * 4900, // minTimeToClick
251
+ 1 + self::secureRandom() * 9999, // maxClickVariance
252
+ self::secureRandom() * 0.5, // minMouseEntropy
253
+ floor(self::secureRandom() * 10) // minScrollEvents
254
+ ];
255
+ };
256
+
257
+ $crossover = fn ($s1, $s2) => array_map(fn ($a, $b) => ($a + $b) / 2, $s1, $s2);
258
+
259
+ $mutate = function (array $solution): array {
260
+ $i = random_int(0, 3);
261
+ $mutationFactors = [500, 1000, 0.1, 2];
262
+ $solution[$i] += (self::secureRandom() - 0.5) * $mutationFactors[$i];
263
+ return $solution;
264
+ };
265
+
266
+ return Optimization::geneticAlgorithmMultiObjective(
267
+ $createIndividual,
268
+ $fitnessFunction,
269
+ $crossover,
270
+ $mutate,
271
+ array_merge(['generations' => 80, 'populationSize' => 60], $options)
272
+ );
273
+ }
274
+
275
+ /**
276
+ * Placeholder pour le solveur TSP.
277
+ * @param array $cities
278
+ * @param array $options
279
+ * @return array
280
+ */
281
+ public static function solveTSP(array $cities, array $options = []): array
282
+ {
283
+ // Implémentation factice pour la complétude
284
+ return ['solution' => array_keys($cities), 'energy' => 100];
285
+ }
286
+
287
+ /**
288
+ * Placeholder pour le solveur de portefeuille.
289
+ * @param array $assets
290
+ * @param float $maxVolatility
291
+ * @param array $options
292
+ * @return array
293
+ */
294
+ public static function solvePortfolio(array $assets, float $maxVolatility, array $options = []): array
295
+ {
296
+ // Implémentation factice pour la complétude
297
+ return ['solution' => array_fill(0, count($assets), 1 / count($assets)), 'fitness' => -0.1];
298
+ }
299
+
300
+ public static function solveFacilityLocation(array $customers, int $numFacilities, array $bounds, array $options = []): array
301
+ {
302
+ $fixedCostPerFacility = $options['fixedCostPerFacility'] ?? 0;
303
+ $initialTemperature = $options['initialTemperature'] ?? 100000.0;
304
+ $coolingRate = $options['coolingRate'] ?? 0.999;
305
+ $maxIterations = $options['maxIterations'] ?? 15000; // Borne raisonnable pour PHP
306
+
307
+ $evaluator = function (array $facilities) use ($customers, $fixedCostPerFacility): float {
308
+ $totalConnectionCost = 0.0;
309
+ foreach ($customers as $customer) {
310
+ $minDistanceSq = INF;
311
+ foreach ($facilities as $facility) {
312
+ $dx = $customer['x'] - $facility['x'];
313
+ $dy = $customer['y'] - $facility['y'];
314
+ $dSq = $dx * $dx + $dy * $dy;
315
+ if ($dSq < $minDistanceSq) {
316
+ $minDistanceSq = $dSq;
317
+ }
318
+ }
319
+ $totalConnectionCost += sqrt($minDistanceSq);
320
+ }
321
+ return $totalConnectionCost + count($facilities) * $fixedCostPerFacility;
322
+ };
323
+
324
+ $neighbor = function (array $facilities) use ($bounds, $numFacilities): array {
325
+ $newFacilities = $facilities;
326
+ $i = random_int(0, $numFacilities - 1);
327
+
328
+ $moveX = (self::secureRandom() - 0.5) * ($bounds['maxX'] - $bounds['minX']) * 0.1;
329
+ $moveY = (self::secureRandom() - 0.5) * ($bounds['maxY'] - $bounds['minY']) * 0.1;
330
+
331
+ $newFacilities[$i]['x'] = max($bounds['minX'], min($bounds['maxX'], $newFacilities[$i]['x'] + $moveX));
332
+ $newFacilities[$i]['y'] = max($bounds['minY'], min($bounds['maxY'], $newFacilities[$i]['y'] + $moveY));
333
+
334
+ return $newFacilities;
335
+ };
336
+
337
+ // Génération d'une solution initiale aléatoire
338
+ $currentSolution = [];
339
+ for ($i = 0; $i < $numFacilities; $i++) {
340
+ $currentSolution[] = [
341
+ 'x' => $bounds['minX'] + self::secureRandom() * ($bounds['maxX'] - $bounds['minX']),
342
+ 'y' => $bounds['minY'] + self::secureRandom() * ($bounds['maxY'] - $bounds['minY']),
343
+ ];
344
+ }
345
+
346
+ $currentEnergy = $evaluator($currentSolution);
347
+ $bestSolution = $currentSolution;
348
+ $bestEnergy = $currentEnergy;
349
+ $temperature = $initialTemperature;
350
+
351
+ for ($step = 0; $step < $maxIterations; $step++) {
352
+ $newSolution = $neighbor($currentSolution);
353
+ $newEnergy = $evaluator($newSolution);
354
+
355
+ $acceptanceProbability = exp(($currentEnergy - $newEnergy) / $temperature);
356
+
357
+ if ($newEnergy < $currentEnergy || self::secureRandom() < $acceptanceProbability) {
358
+ $currentSolution = $newSolution;
359
+ $currentEnergy = $newEnergy;
360
+ }
361
+
362
+ if ($currentEnergy < $bestEnergy) {
363
+ $bestSolution = $currentSolution;
364
+ $bestEnergy = $currentEnergy;
365
+ }
366
+
367
+ $temperature *= $coolingRate;
368
+ }
369
+
370
+ return ['solution' => $bestSolution, 'energy' => $bestEnergy];
371
+ }
372
+
373
+ /**
374
+ * Évalue de manière indépendante l'énergie d'une solution de placement d'infrastructures.
375
+ * Appelée par le serveur pour valider les calculs soumis par le client.
376
+ *
377
+ * @param array $facilities Liste des positions proposées par le client.
378
+ * @param array $payload Configuration initiale contenant les clients et les coûts fixes.
379
+ * @return float Le coût total vérifié.
380
+ */
381
+ public static function evaluateFacilityLocation(array $facilities, array $payload): float
382
+ {
383
+ $customers = $payload['customers'] ?? [];
384
+ $fixedCostPerFacility = $payload['options']['fixedCostPerFacility'] ?? 0;
385
+ $totalConnectionCost = 0.0;
386
+
387
+ foreach ($customers as $customer) {
388
+ $minDistanceSq = INF;
389
+ foreach ($facilities as $facility) {
390
+ $dx = $customer['x'] - $facility['x'];
391
+ $dy = $customer['y'] - $facility['y'];
392
+ $dSq = $dx * $dx + $dy * $dy;
393
+ if ($dSq < $minDistanceSq) {
394
+ $minDistanceSq = $dSq;
395
+ }
396
+ }
397
+ $totalConnectionCost += sqrt($minDistanceSq);
398
+ }
399
+
400
+ return $totalConnectionCost + count($facilities) * $fixedCostPerFacility;
401
+ }
305
402
  }