@anonympins/fingerprint 0.3.2 → 0.3.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.
- package/CHANGELOG.md +172 -0
- package/README.md +276 -34
- package/composer.json +38 -0
- package/index.js +5 -0
- package/package.json +23 -18
- package/phpunit.xml +20 -0
- package/public/fp.js +2 -0
- package/src/js/build-client.js +69 -0
- package/{fingerprint.builder.js → src/js/fingerprint.builder.js} +168 -175
- package/{fingerprint.client.js → src/js/fingerprint.client.js} +634 -538
- package/src/js/fingerprint.client.obfuscated.js +1 -0
- package/{fingerprint.js → src/js/fingerprint.js} +255 -101
- package/{library.js → src/js/library.js} +1729 -1729
- package/{problem-manager.js → src/js/problem-manager.js} +539 -522
- package/src/php/AutoTuner.php +155 -0
- package/src/php/Challenge/ChallengeUtils.php +306 -0
- package/src/php/Config/SecurityProfiles.php +257 -0
- package/src/php/DirectFingerprint.php +81 -0
- package/src/php/FingerprintBuilder.php +185 -0
- package/src/php/FingerprintClient.php +118 -0
- package/src/php/FingerprintEngine.php +850 -0
- package/src/php/Optimization/FunctionRegistry.php +63 -0
- package/src/php/Optimization/Optimization.php +256 -0
- package/src/php/Optimization/OptimizationOperators.php +305 -0
- package/src/php/Optimization/ProblemInitializers.php +53 -0
- package/src/php/ProblemManager.php +255 -0
- package/src/php/RequestContext.php +87 -0
- package/src/php/Store/IStore.php +42 -0
- package/src/php/Store/InMemoryStore.php +67 -0
- package/src/php/Store/StoreManager.php +26 -0
- package/src/php/Tests/ChallengeUtilsTest.php +82 -0
- package/src/php/Tests/FingerprintBuilderTest.php +58 -0
- package/src/php/Tests/FingerprintEngineTest.php +219 -0
- package/src/php/Tests/PowTest.php +40 -0
- package/src/php/Tests/ProblemManagerTest.php +295 -0
- package/src/php/Tests/RequestUtilsTest.php +81 -0
- package/src/php/Tests/problems.config.json +9 -0
- package/src/php/Utils/BigInt.php +102 -0
- package/src/php/Utils/BlockList.php +100 -0
- package/src/php/Utils/Logger.php +30 -0
- package/src/php/Utils/MaliciousPatterns.php +59 -0
- package/src/php/Utils/RequestUtils.php +673 -0
- package/fingerprint.client.obfuscated.js +0 -1
- /package/{mongodb-store.js → src/js/mongodb-store.js} +0 -0
- /package/{optimization.worker.js → src/js/optimization.worker.js} +0 -0
- /package/{pow.solver.inline.js → src/js/pow.solver.inline.js} +0 -0
- /package/{pow.solver.js → src/js/pow.solver.js} +0 -0
- /package/{pow.worker.js → src/js/pow.worker.js} +0 -0
- /package/{redis-store.js → src/js/redis-store.js} +0 -0
- /package/{sql-store.js → src/js/sql-store.js} +0 -0
|
@@ -1,523 +1,540 @@
|
|
|
1
|
-
import { promises as fs } from 'node:fs';
|
|
2
|
-
import { Optimization } from './library.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @namespace FunctionRegistry
|
|
6
|
-
* @description Registre pour exposer de manière contrôlée les fonctions de la bibliothèque.
|
|
7
|
-
* Permet de les appeler dynamiquement depuis la configuration des problèmes.
|
|
8
|
-
* Utilise la notation par points pour accéder aux fonctions imbriquées (ex: 'tsp.calculateEnergy').
|
|
9
|
-
*/
|
|
10
|
-
const FunctionRegistry = {};
|
|
11
|
-
|
|
12
|
-
// --- Fonctions de "Scoring" (évaluation d'une solution) ---
|
|
13
|
-
// Ces fonctions sont des adaptateurs pour utiliser les utilitaires de la bibliothèque
|
|
14
|
-
// avec la structure attendue par le ProblemManager.
|
|
15
|
-
FunctionRegistry['cpc.solve'] = Optimization.Operators.solveOptimalCPC; // NOUVEAU: Enregistrement du solveur CPC
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
/**
|
|
19
|
-
* Évalue la distance totale d'un chemin pour le problème du voyageur de commerce (TSP).
|
|
20
|
-
* @param {Array<{x: number, y: number}>} path - Un tableau de points représentant le chemin.
|
|
21
|
-
* @returns {number} La distance totale du chemin.
|
|
22
|
-
*/
|
|
23
|
-
FunctionRegistry['tsp.calculateEnergy'] = (path) => {
|
|
24
|
-
// Crée un tableau d'indices [0, 1, 2, ...] pour la fonction evaluatePathDistance.
|
|
25
|
-
const indices = Array.from({ length: path.length }, (_, i) => i);
|
|
26
|
-
return Optimization.Utils.evaluatePathDistance(path, indices);
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Évalue les métriques d'un portefeuille (rendement et volatilité).
|
|
31
|
-
* Pour l'instant, retourne le rendement négatif pour correspondre à l'objectif de minimisation
|
|
32
|
-
* de l'algorithme génétique de la bibliothèque.
|
|
33
|
-
* @param {Array<number>} weights - Les poids des actifs dans le portefeuille.
|
|
34
|
-
* @param {object} payload - Le payload du problème, contenant les actifs.
|
|
35
|
-
* @returns {number} Le rendement négatif du portefeuille.
|
|
36
|
-
*/
|
|
37
|
-
FunctionRegistry['portfolio.calculateMetrics'] = (weights, payload) => {
|
|
38
|
-
const { assets, maxVolatility } = payload;
|
|
39
|
-
// On utilise l'opérateur de la bibliothèque pour créer la fonction de fitness
|
|
40
|
-
// et on l'appelle immédiatement.
|
|
41
|
-
const fitnessFunction = Optimization.Operators.createPortfolioAllocator({
|
|
42
|
-
assets,
|
|
43
|
-
maxVolatility,
|
|
44
|
-
});
|
|
45
|
-
// La fonction de fitness retourne le rendement négatif, ce qui est ce que nous voulons
|
|
46
|
-
// stocker comme "énergie" ou score.
|
|
47
|
-
return fitnessFunction(weights);
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
// --- Fonctions de "Résolution" (algorithmes complets) ---
|
|
51
|
-
// Utiles pour les workers qui exécutent une tâche de bout en bout.
|
|
52
|
-
FunctionRegistry['tsp.solve'] = Optimization.Operators.solveTSP;
|
|
53
|
-
FunctionRegistry['portfolio.solve'] = Optimization.Operators.solvePortfolio;
|
|
54
|
-
FunctionRegistry['fraud.solve'] = Optimization.Operators.solveFraudDetection; // NOUVEAU: Enregistrement du solveur de fraude
|
|
55
|
-
FunctionRegistry['facility.solve'] = Optimization.Operators.solveFacilityLocation;
|
|
56
|
-
FunctionRegistry['security.tune'] = Optimization.Operators.solveFullSecurityTuning;
|
|
57
|
-
|
|
58
|
-
// --- Fonctions "Utilitaires" ---
|
|
59
|
-
FunctionRegistry['utils.evaluatePathDistance'] = Optimization.Utils.evaluatePathDistance;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* @namespace ProblemInitializers
|
|
64
|
-
* @description Fonctions pour générer dynamiquement les données d'un problème.
|
|
65
|
-
*/
|
|
66
|
-
const ProblemInitializers = {
|
|
67
|
-
/**
|
|
68
|
-
* Génère un ensemble de points aléatoires pour un problème de TSP.
|
|
69
|
-
* @param {object} params - Les paramètres de génération.
|
|
70
|
-
* @param {number} params.count - Le nombre de points à générer.
|
|
71
|
-
* @param {{x: number, y: number}} [params.bounds={x: 1000, y: 1000}] - Les limites spatiales.
|
|
72
|
-
* @returns {Array<{x: number, y: number}>}
|
|
73
|
-
*/
|
|
74
|
-
'generate:randomPoints': (params) => {
|
|
75
|
-
const { count, bounds = { x: 1000, y: 1000 } } = params;
|
|
76
|
-
if (isNaN(count)) return [];
|
|
77
|
-
return Array.from({ length: count }, () => ({ x: Math.random() * bounds.x, y: Math.random() * bounds.y }));
|
|
78
|
-
},
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Génère un ensemble d'actifs financiers aléatoires pour un problème de portefeuille.
|
|
82
|
-
* @param {object} params - Les paramètres de génération.
|
|
83
|
-
* @param {number} params.count - Le nombre d'actifs à générer.
|
|
84
|
-
* @returns {Array<{expectedReturn: number, volatility: number}>}
|
|
85
|
-
*/
|
|
86
|
-
'generate:randomAssets': (params) => {
|
|
87
|
-
const { count } = params;
|
|
88
|
-
if (isNaN(count)) return [];
|
|
89
|
-
return Array.from({ length: count }, () => ({
|
|
90
|
-
expectedReturn: Math.random() * 0.2,
|
|
91
|
-
volatility: 0.1 + Math.random() * 0.3
|
|
92
|
-
}));
|
|
93
|
-
},
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Crée une fonction qui génère des arguments pour chaque worker de `runMultipleParallel`.
|
|
97
|
-
* Permet de faire varier les paramètres (ex: solution initiale) pour chaque cycle.
|
|
98
|
-
* @param {object} params - Les paramètres de configuration.
|
|
99
|
-
* @param {Array<any>} params.baseArgs - Les arguments de base, communs à tous les workers.
|
|
100
|
-
* @param {object} params.variations - Décrit comment faire varier un argument.
|
|
101
|
-
* @returns {function(number): Array<any>} La fonction `workerDataGenerator`.
|
|
102
|
-
*/
|
|
103
|
-
'generate:parallelArgs': (params) => {
|
|
104
|
-
const { baseArgs, variations } = params;
|
|
105
|
-
return (cycleIndex) => {
|
|
106
|
-
const cycleArgs = [...baseArgs];
|
|
107
|
-
// Pour l'instant, on gère la variation de la solution initiale pour le TSP
|
|
108
|
-
if (variations?.initialSolution === 'random') {
|
|
109
|
-
cycleArgs[0] = cycleArgs[0].sort(() => Math.random() - 0.5);
|
|
110
|
-
}
|
|
111
|
-
return cycleArgs;
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
class ProblemManager {
|
|
117
|
-
/**
|
|
118
|
-
* @private
|
|
119
|
-
* Le constructeur est privé. Utilisez la méthode de fabrique asynchrone `create()`.
|
|
120
|
-
* @param {
|
|
121
|
-
* @param {
|
|
122
|
-
* @param {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
this.
|
|
128
|
-
this.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
*
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
task.
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
task.
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
task.
|
|
240
|
-
task.
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
task.
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
task.
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
//
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
//
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
problem
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
* @
|
|
502
|
-
*/
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { Optimization } from './library.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @namespace FunctionRegistry
|
|
6
|
+
* @description Registre pour exposer de manière contrôlée les fonctions de la bibliothèque.
|
|
7
|
+
* Permet de les appeler dynamiquement depuis la configuration des problèmes.
|
|
8
|
+
* Utilise la notation par points pour accéder aux fonctions imbriquées (ex: 'tsp.calculateEnergy').
|
|
9
|
+
*/
|
|
10
|
+
const FunctionRegistry = {};
|
|
11
|
+
|
|
12
|
+
// --- Fonctions de "Scoring" (évaluation d'une solution) ---
|
|
13
|
+
// Ces fonctions sont des adaptateurs pour utiliser les utilitaires de la bibliothèque
|
|
14
|
+
// avec la structure attendue par le ProblemManager.
|
|
15
|
+
FunctionRegistry['cpc.solve'] = Optimization.Operators.solveOptimalCPC; // NOUVEAU: Enregistrement du solveur CPC
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
/**
|
|
19
|
+
* Évalue la distance totale d'un chemin pour le problème du voyageur de commerce (TSP).
|
|
20
|
+
* @param {Array<{x: number, y: number}>} path - Un tableau de points représentant le chemin.
|
|
21
|
+
* @returns {number} La distance totale du chemin.
|
|
22
|
+
*/
|
|
23
|
+
FunctionRegistry['tsp.calculateEnergy'] = (path) => {
|
|
24
|
+
// Crée un tableau d'indices [0, 1, 2, ...] pour la fonction evaluatePathDistance.
|
|
25
|
+
const indices = Array.from({ length: path.length }, (_, i) => i);
|
|
26
|
+
return Optimization.Utils.evaluatePathDistance(path, indices);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Évalue les métriques d'un portefeuille (rendement et volatilité).
|
|
31
|
+
* Pour l'instant, retourne le rendement négatif pour correspondre à l'objectif de minimisation
|
|
32
|
+
* de l'algorithme génétique de la bibliothèque.
|
|
33
|
+
* @param {Array<number>} weights - Les poids des actifs dans le portefeuille.
|
|
34
|
+
* @param {object} payload - Le payload du problème, contenant les actifs.
|
|
35
|
+
* @returns {number} Le rendement négatif du portefeuille.
|
|
36
|
+
*/
|
|
37
|
+
FunctionRegistry['portfolio.calculateMetrics'] = (weights, payload) => {
|
|
38
|
+
const { assets, maxVolatility } = payload;
|
|
39
|
+
// On utilise l'opérateur de la bibliothèque pour créer la fonction de fitness
|
|
40
|
+
// et on l'appelle immédiatement.
|
|
41
|
+
const fitnessFunction = Optimization.Operators.createPortfolioAllocator({
|
|
42
|
+
assets,
|
|
43
|
+
maxVolatility,
|
|
44
|
+
});
|
|
45
|
+
// La fonction de fitness retourne le rendement négatif, ce qui est ce que nous voulons
|
|
46
|
+
// stocker comme "énergie" ou score.
|
|
47
|
+
return fitnessFunction(weights);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// --- Fonctions de "Résolution" (algorithmes complets) ---
|
|
51
|
+
// Utiles pour les workers qui exécutent une tâche de bout en bout.
|
|
52
|
+
FunctionRegistry['tsp.solve'] = Optimization.Operators.solveTSP;
|
|
53
|
+
FunctionRegistry['portfolio.solve'] = Optimization.Operators.solvePortfolio;
|
|
54
|
+
FunctionRegistry['fraud.solve'] = Optimization.Operators.solveFraudDetection; // NOUVEAU: Enregistrement du solveur de fraude
|
|
55
|
+
FunctionRegistry['facility.solve'] = Optimization.Operators.solveFacilityLocation;
|
|
56
|
+
FunctionRegistry['security.tune'] = Optimization.Operators.solveFullSecurityTuning;
|
|
57
|
+
|
|
58
|
+
// --- Fonctions "Utilitaires" ---
|
|
59
|
+
FunctionRegistry['utils.evaluatePathDistance'] = Optimization.Utils.evaluatePathDistance;
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @namespace ProblemInitializers
|
|
64
|
+
* @description Fonctions pour générer dynamiquement les données d'un problème.
|
|
65
|
+
*/
|
|
66
|
+
const ProblemInitializers = {
|
|
67
|
+
/**
|
|
68
|
+
* Génère un ensemble de points aléatoires pour un problème de TSP.
|
|
69
|
+
* @param {object} params - Les paramètres de génération.
|
|
70
|
+
* @param {number} params.count - Le nombre de points à générer.
|
|
71
|
+
* @param {{x: number, y: number}} [params.bounds={x: 1000, y: 1000}] - Les limites spatiales.
|
|
72
|
+
* @returns {Array<{x: number, y: number}>}
|
|
73
|
+
*/
|
|
74
|
+
'generate:randomPoints': (params) => {
|
|
75
|
+
const { count, bounds = { x: 1000, y: 1000 } } = params;
|
|
76
|
+
if (isNaN(count)) return [];
|
|
77
|
+
return Array.from({ length: count }, () => ({ x: Math.random() * bounds.x, y: Math.random() * bounds.y }));
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Génère un ensemble d'actifs financiers aléatoires pour un problème de portefeuille.
|
|
82
|
+
* @param {object} params - Les paramètres de génération.
|
|
83
|
+
* @param {number} params.count - Le nombre d'actifs à générer.
|
|
84
|
+
* @returns {Array<{expectedReturn: number, volatility: number}>}
|
|
85
|
+
*/
|
|
86
|
+
'generate:randomAssets': (params) => {
|
|
87
|
+
const { count } = params;
|
|
88
|
+
if (isNaN(count)) return [];
|
|
89
|
+
return Array.from({ length: count }, () => ({
|
|
90
|
+
expectedReturn: Math.random() * 0.2,
|
|
91
|
+
volatility: 0.1 + Math.random() * 0.3
|
|
92
|
+
}));
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Crée une fonction qui génère des arguments pour chaque worker de `runMultipleParallel`.
|
|
97
|
+
* Permet de faire varier les paramètres (ex: solution initiale) pour chaque cycle.
|
|
98
|
+
* @param {object} params - Les paramètres de configuration.
|
|
99
|
+
* @param {Array<any>} params.baseArgs - Les arguments de base, communs à tous les workers.
|
|
100
|
+
* @param {object} params.variations - Décrit comment faire varier un argument.
|
|
101
|
+
* @returns {function(number): Array<any>} La fonction `workerDataGenerator`.
|
|
102
|
+
*/
|
|
103
|
+
'generate:parallelArgs': (params) => {
|
|
104
|
+
const { baseArgs, variations } = params;
|
|
105
|
+
return (cycleIndex) => {
|
|
106
|
+
const cycleArgs = [...baseArgs];
|
|
107
|
+
// Pour l'instant, on gère la variation de la solution initiale pour le TSP
|
|
108
|
+
if (variations?.initialSolution === 'random') {
|
|
109
|
+
cycleArgs[0] = cycleArgs[0].sort(() => Math.random() - 0.5);
|
|
110
|
+
}
|
|
111
|
+
return cycleArgs;
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
class ProblemManager {
|
|
117
|
+
/**
|
|
118
|
+
* @private
|
|
119
|
+
* Le constructeur est privé. Utilisez la méthode de fabrique asynchrone `create()`.
|
|
120
|
+
* @param {object} options - Les options d'initialisation.
|
|
121
|
+
* @param {string} [options.configPath] - Le chemin vers le fichier de configuration.
|
|
122
|
+
* @param {object} [options.config] - L'objet de configuration des problèmes.
|
|
123
|
+
* @param {Array<object>} problems - Les problèmes pré-chargés.
|
|
124
|
+
* @param {IStore} store - The datastore for synchronization.
|
|
125
|
+
*/
|
|
126
|
+
constructor(options, problems, store) {
|
|
127
|
+
this.configPath = options.configPath;
|
|
128
|
+
this.config = options.config;
|
|
129
|
+
this.problems = problems;
|
|
130
|
+
this.store = store; // The datastore instance
|
|
131
|
+
this.currentProblemIndex = 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Méthode de fabrique asynchrone pour créer et initialiser une instance de ProblemManager.
|
|
136
|
+
* @param {object} options - Les options d'initialisation.
|
|
137
|
+
* @returns {Promise<ProblemManager>}
|
|
138
|
+
*/
|
|
139
|
+
static async create(options, store) {
|
|
140
|
+
const manager = new ProblemManager(options, [], store);
|
|
141
|
+
manager.problems = await manager.loadProblems();
|
|
142
|
+
return manager;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Charge et parse les problèmes depuis le fichier de configuration de manière asynchrone.
|
|
147
|
+
* It now also synchronizes with the datastore.
|
|
148
|
+
* @returns {Promise<Array<object>>}
|
|
149
|
+
*/
|
|
150
|
+
async loadProblems() {
|
|
151
|
+
// Guard clause: If no store is configured (e.g., during isolated test imports),
|
|
152
|
+
// do not attempt to load problems to prevent crashes.
|
|
153
|
+
if (!this.store) {
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
let problemsFromFile;
|
|
159
|
+
if (this.config) {
|
|
160
|
+
problemsFromFile = this.config;
|
|
161
|
+
} else if (this.configPath) {
|
|
162
|
+
const data = await fs.readFile(this.configPath, 'utf-8');
|
|
163
|
+
problemsFromFile = JSON.parse(data);
|
|
164
|
+
} else {
|
|
165
|
+
throw new Error('Either `config` or `configPath` must be provided to load problems.');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// For each problem, try to load its state from the datastore.
|
|
169
|
+
// If it doesn't exist, use the state from the file and save it to the store.
|
|
170
|
+
const problems = await Promise.all(problemsFromFile.map(async (problem) => {
|
|
171
|
+
const storeKey = `problem-state:${problem.id}`;
|
|
172
|
+
let storedState = await this.store.get(storeKey);
|
|
173
|
+
|
|
174
|
+
if (!storedState) {
|
|
175
|
+
storedState = problem.state; // Use initial state from file
|
|
176
|
+
await this.store.set(storeKey, storedState); // Persist initial state
|
|
177
|
+
}
|
|
178
|
+
problem.state = storedState;
|
|
179
|
+
return problem;
|
|
180
|
+
}));
|
|
181
|
+
// Initialisation dynamique des problèmes
|
|
182
|
+
for (const problem of problems) {
|
|
183
|
+
// Résolution des fonctions via le registre
|
|
184
|
+
if (problem.workUnit.scoreFunction) {
|
|
185
|
+
problem.workUnit.scoreFunction = FunctionRegistry[problem.workUnit.scoreFunction] || null;
|
|
186
|
+
}
|
|
187
|
+
for (const key in problem.payload) {
|
|
188
|
+
const value = problem.payload[key];
|
|
189
|
+
// On cherche une instruction d'initialisation (ex: { "$init": "generate:randomPoints", ... })
|
|
190
|
+
if (typeof value === 'object' && value !== null && value.$init) {
|
|
191
|
+
const initializer = ProblemInitializers[value.$init];
|
|
192
|
+
// On cherche une instruction de fonction (ex: { "$func": "tsp.calculateEnergy" })
|
|
193
|
+
// Note: Actuellement non utilisé, mais prêt pour une future extension.
|
|
194
|
+
if (initializer) {
|
|
195
|
+
// On remplace l'objet d'instruction par les données générées.
|
|
196
|
+
problem.payload[key] = initializer(value.params || {});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return problems;
|
|
202
|
+
} catch (error) {
|
|
203
|
+
console.error(`[ProblemManager] Erreur lors du chargement du fichier de problèmes: ${error.message}`);
|
|
204
|
+
return []; // Retourne un tableau vide en cas d'erreur pour éviter un crash
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Sélectionne un problème et génère une unité de travail.
|
|
210
|
+
* @param {number} suspicionFactor - Le facteur de suspicion pour ajuster la difficulté.
|
|
211
|
+
* @returns {{problemId: string, task: object}|null}
|
|
212
|
+
*/
|
|
213
|
+
dispatchWork(suspicionFactor) {
|
|
214
|
+
if (this.problems.length === 0) return null;
|
|
215
|
+
|
|
216
|
+
const problem = this.problems[this.currentProblemIndex];
|
|
217
|
+
this.currentProblemIndex = (this.currentProblemIndex + 1) % this.problems.length;
|
|
218
|
+
|
|
219
|
+
const task = { type: problem.workUnit.type };
|
|
220
|
+
const { scalingFactor } = problem.workUnit;
|
|
221
|
+
|
|
222
|
+
switch (problem.workUnit.type) {
|
|
223
|
+
case 'simulated_annealing_iterations':
|
|
224
|
+
// Assurer une difficulté minimale pour que le challenge soit significatif
|
|
225
|
+
const baseIterations = Math.max(15000, problem.workUnit.baseIterations || 0);
|
|
226
|
+
task.iterations = scalingFactor
|
|
227
|
+
? Math.floor(baseIterations * Math.pow(scalingFactor, suspicionFactor))
|
|
228
|
+
: Math.floor(baseIterations * (0.5 + suspicionFactor));
|
|
229
|
+
task.payload = problem.payload;
|
|
230
|
+
task.initialSolution = problem.state.bestSolution;
|
|
231
|
+
break;
|
|
232
|
+
|
|
233
|
+
case 'genetic_algorithm_generations':
|
|
234
|
+
// Assurer une difficulté minimale pour que le challenge soit significatif
|
|
235
|
+
const baseGenerations = Math.max(50, problem.workUnit.baseGenerations || 0);
|
|
236
|
+
task.generations = scalingFactor
|
|
237
|
+
? Math.floor(baseGenerations * Math.pow(scalingFactor, suspicionFactor))
|
|
238
|
+
: Math.floor(baseGenerations * (0.5 + suspicionFactor));
|
|
239
|
+
task.payload = problem.payload;
|
|
240
|
+
task.initialPopulation = problem.state.population;
|
|
241
|
+
break;
|
|
242
|
+
|
|
243
|
+
case 'run_multiple_parallel':
|
|
244
|
+
task.solverName = problem.workUnit.solverName;
|
|
245
|
+
task.numCycles = problem.workUnit.numCycles;
|
|
246
|
+
// Les arguments et le générateur sont dans le payload pour plus de flexibilité
|
|
247
|
+
task.baseSolverArgs = problem.payload.baseSolverArgs;
|
|
248
|
+
task.workerDataGenerator = problem.payload.workerDataGenerator;
|
|
249
|
+
task.logProgress = problem.payload.logProgress || false;
|
|
250
|
+
task.concurrency = problem.payload.concurrency;
|
|
251
|
+
break;
|
|
252
|
+
|
|
253
|
+
case 'multi_objective_genetic_algorithm':
|
|
254
|
+
// La difficulté s'applique au nombre de générations
|
|
255
|
+
const baseGenerationsMulti = Math.max(30, problem.workUnit.baseGenerations || 0);
|
|
256
|
+
task.generations = scalingFactor
|
|
257
|
+
? Math.floor(baseGenerationsMulti * Math.pow(scalingFactor, suspicionFactor))
|
|
258
|
+
: Math.floor(baseGenerationsMulti * (0.5 + suspicionFactor));
|
|
259
|
+
task.payload = problem.payload;
|
|
260
|
+
// L'état initial est le front de Pareto actuel, que le client peut utiliser pour l'élitisme
|
|
261
|
+
task.initialFront = problem.state.paretoFront;
|
|
262
|
+
task.solverName = problem.workUnit.solverName; // Le nom du solveur à utiliser (ex: 'cpc.solve')
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return { problemId: problem.id, task };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Intègre la solution d'un client dans l'état du problème.
|
|
271
|
+
* @param {string} problemId - L'ID du problème.
|
|
272
|
+
* @param {object} solutionData - La solution renvoyée par le client.
|
|
273
|
+
*/
|
|
274
|
+
async integrateSolution(problemId, solutionData) {
|
|
275
|
+
const problem = this.problems.find(p => p.id === problemId);
|
|
276
|
+
if (!problem) return;
|
|
277
|
+
const storeKey = `problem-state:${problem.id}`;
|
|
278
|
+
switch (problem.workUnit.type) {
|
|
279
|
+
case 'simulated_annealing_iterations':
|
|
280
|
+
// 1. Ne JAMAIS faire confiance au score du client. Recalculer systématiquement.
|
|
281
|
+
const scoreFunction = problem.workUnit.scoreFunction;
|
|
282
|
+
if (!scoreFunction) {
|
|
283
|
+
console.error(`[ProblemManager] Aucune fonction de score définie pour ${problemId}. Impossible de vérifier la solution.`);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const recalculatedEnergy = scoreFunction(solutionData.solution, problem.payload);
|
|
287
|
+
|
|
288
|
+
const currentBest = parseFloat(problem.state.bestEnergy) || Infinity;
|
|
289
|
+
// 2. Comparer le score recalculé, pas celui du client.
|
|
290
|
+
const isBetter = recalculatedEnergy < currentBest;
|
|
291
|
+
|
|
292
|
+
if (isBetter) {
|
|
293
|
+
problem.state.bestSolution = solutionData.solution;
|
|
294
|
+
problem.state.bestEnergy = recalculatedEnergy; // 3. Stocker le score vérifié.
|
|
295
|
+
problem.state.lastUpdate = new Date().toISOString();
|
|
296
|
+
console.log(`[ProblemManager] Nouvelle meilleure solution pour ${problemId}: ${recalculatedEnergy.toFixed(2)}`);
|
|
297
|
+
}
|
|
298
|
+
break;
|
|
299
|
+
case 'genetic_algorithm_generations':
|
|
300
|
+
// VÉRIFICATION PAR ÉCHANTILLONNAGE pour équilibrer sécurité et performance.
|
|
301
|
+
const fitnessFunction = FunctionRegistry['portfolio.calculateMetrics']; // Ou une fonction plus générique
|
|
302
|
+
if (!fitnessFunction || !solutionData.population || solutionData.population.length === 0) {
|
|
303
|
+
console.error(`[ProblemManager] Impossible de vérifier la population pour ${problemId}.`);
|
|
304
|
+
return; // Ne rien faire si la vérification est impossible.
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// 1. On choisit un petit échantillon aléatoire de la population soumise.
|
|
308
|
+
const sampleSize = Math.min(5, solutionData.population.length);
|
|
309
|
+
const sampleIndices = new Set();
|
|
310
|
+
while (sampleIndices.size < sampleSize) {
|
|
311
|
+
sampleIndices.add(Math.floor(Math.random() * solutionData.population.length));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// 2. On recalcule le score pour cet échantillon.
|
|
315
|
+
let totalRecalculatedFitness = 0;
|
|
316
|
+
for (const index of sampleIndices) {
|
|
317
|
+
const individual = solutionData.population[index];
|
|
318
|
+
totalRecalculatedFitness += fitnessFunction(individual.chromosome, problem.payload);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
problem.state.population = solutionData.population; // On accepte la population
|
|
322
|
+
console.log(`[ProblemManager] Population mise à jour pour ${problemId}. Fitness moyen de l'échantillon: ${(totalRecalculatedFitness / sampleSize).toFixed(4)}`);
|
|
323
|
+
break;
|
|
324
|
+
|
|
325
|
+
case 'multi_objective_genetic_algorithm':
|
|
326
|
+
// Pour le multi-objectifs, on fusionne le front de Pareto existant avec celui du client.
|
|
327
|
+
await this._integrateParetoFront(problem, solutionData.paretoFront);
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
// Persist the updated state to the datastore immediately.
|
|
331
|
+
await this.store.set(storeKey, problem.state);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* S'assure qu'un problème a une solution initiale. Si non, en génère une.
|
|
336
|
+
* @param {object} problem - L'objet problème.
|
|
337
|
+
* @private
|
|
338
|
+
*/
|
|
339
|
+
async _ensureInitialSolution(problem) {
|
|
340
|
+
if (problem.state.bestSolution) {
|
|
341
|
+
return; // Une solution existe déjà
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
console.log(`[ProblemManager] Génération d'une solution initiale pour le problème ${problem.id}...`);
|
|
345
|
+
|
|
346
|
+
// On utilise la fonction de score définie dans la config
|
|
347
|
+
const scoreFunction = problem.workUnit.scoreFunction;
|
|
348
|
+
// On suppose que la source de la solution initiale est définie dans la config
|
|
349
|
+
const initialSolutionSource = problem.payload[problem.workUnit.initialSolutionSource];
|
|
350
|
+
|
|
351
|
+
if (scoreFunction && initialSolutionSource && Array.isArray(initialSolutionSource)) {
|
|
352
|
+
const initialSolution = initialSolutionSource;
|
|
353
|
+
// On calcule le score (énergie, fitness, etc.) de cette solution initiale.
|
|
354
|
+
// La fonction de scoring peut nécessiter des arguments supplémentaires du payload.
|
|
355
|
+
const score = scoreFunction(initialSolution, problem.payload);
|
|
356
|
+
|
|
357
|
+
problem.state.bestSolution = initialSolution;
|
|
358
|
+
// Le nom de la propriété du score dépend du type de problème
|
|
359
|
+
problem.state.bestEnergy = score; // Pourrait être généralisé si besoin
|
|
360
|
+
problem.state.lastUpdate = new Date().toISOString();
|
|
361
|
+
|
|
362
|
+
console.log(`[ProblemManager] Solution initiale pour ${problem.id} générée avec un score de ${score.toFixed(2)}.`);
|
|
363
|
+
// Save the newly generated initial solution to the store.
|
|
364
|
+
await this.store.set(`problem-state:${problem.id}`, problem.state);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Intègre un nouveau front de Pareto dans l'état du problème.
|
|
370
|
+
* @param {object} problem - L'objet problème.
|
|
371
|
+
* @param {Array<object>} newFront - Le front de Pareto renvoyé par un client.
|
|
372
|
+
* @private
|
|
373
|
+
*/
|
|
374
|
+
async _integrateParetoFront(problem, newFront) {
|
|
375
|
+
if (!Array.isArray(newFront) || newFront.length === 0) return;
|
|
376
|
+
|
|
377
|
+
const currentFront = problem.state.paretoFront || []; // eslint-disable-line no-unused-vars
|
|
378
|
+
const combined = [...currentFront, ...newFront];
|
|
379
|
+
|
|
380
|
+
// --- Logique de tri non-dominé pour trouver le nouveau meilleur front ---
|
|
381
|
+
const paretoDominates = (a, b) => {
|
|
382
|
+
let aIsBetterInOne = false;
|
|
383
|
+
// On suppose que les objectifs sont à minimiser
|
|
384
|
+
for (let i = 0; i < a.objectives.length; i++) {
|
|
385
|
+
if (a.objectives[i] > b.objectives[i]) return false; // A est pire sur au moins un objectif
|
|
386
|
+
if (a.objectives[i] < b.objectives[i]) aIsBetterInOne = true; // A est strictement meilleur sur au moins un
|
|
387
|
+
}
|
|
388
|
+
return aIsBetterInOne;
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
const nextFront = [];
|
|
392
|
+
const dominatedIndices = new Set();
|
|
393
|
+
|
|
394
|
+
for (let i = 0; i < combined.length; i++) {
|
|
395
|
+
if (dominatedIndices.has(i)) continue;
|
|
396
|
+
let isDominated = false;
|
|
397
|
+
for (let j = 0; j < combined.length; j++) {
|
|
398
|
+
if (i === j || dominatedIndices.has(j)) continue;
|
|
399
|
+
if (paretoDominates(combined[j], combined[i])) {
|
|
400
|
+
isDominated = true;
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
if (paretoDominates(combined[i], combined[j])) {
|
|
404
|
+
dominatedIndices.add(j);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (!isDominated) {
|
|
408
|
+
nextFront.push(combined[i]);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Update if the new front is different in size OR content.
|
|
413
|
+
// Stringifying is a simple way to check for content changes.
|
|
414
|
+
const hasContentChanged = JSON.stringify(nextFront) !== JSON.stringify(problem.state.paretoFront);
|
|
415
|
+
if (hasContentChanged) {
|
|
416
|
+
console.log(`[ProblemManager] Nouveau front de Pareto pour ${problem.id} avec ${nextFront.length} solutions (précédemment ${currentFront.length}).`);
|
|
417
|
+
problem.state.paretoFront = nextFront;
|
|
418
|
+
problem.state.lastUpdate = new Date().toISOString();
|
|
419
|
+
await this.store.set(`problem-state:${problem.id}`, problem.state);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Récupère la meilleure solution actuellement connue pour un ou plusieurs problèmes.
|
|
425
|
+
* @param {string} [problemId] - L'ID optionnel du problème à consulter.
|
|
426
|
+
* Si non fourni, retourne les meilleures solutions pour tous les problèmes.
|
|
427
|
+
* @returns {object|Array<object>|null}
|
|
428
|
+
* - Si un `problemId` est fourni, retourne un objet `{ id, solution, score }` ou `null` si non trouvé.
|
|
429
|
+
* - Si aucun `problemId` n'est fourni, retourne un tableau de ces objets.
|
|
430
|
+
*/
|
|
431
|
+
async getBestSolutions(problemId) {
|
|
432
|
+
const problemsToProcess = problemId
|
|
433
|
+
? this.problems.filter(p => p.id === problemId)
|
|
434
|
+
: this.problems;
|
|
435
|
+
|
|
436
|
+
// On ne génère une solution initiale que pour les problèmes mono-objectif
|
|
437
|
+
for (const p of problemsToProcess.filter(p => p.workUnit.type !== 'multi_objective_genetic_algorithm')) {
|
|
438
|
+
await this._ensureInitialSolution(p);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const formatSolution = (p) => {
|
|
442
|
+
// Après _ensureInitialSolution, on peut supposer que p.state existe.
|
|
443
|
+
if (!p || !p.state) return null;
|
|
444
|
+
|
|
445
|
+
// Cas spécial pour les problèmes multi-objectifs
|
|
446
|
+
if (p.workUnit.type === 'multi_objective_genetic_algorithm') {
|
|
447
|
+
return {
|
|
448
|
+
id: p.id,
|
|
449
|
+
solution: p.state.paretoFront, // La "solution" est l'ensemble du front
|
|
450
|
+
score: p.state.paretoFront?.length || 0, // Le "score" est le nombre de points sur le front
|
|
451
|
+
lastUpdate: p.state.lastUpdate,
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return {
|
|
456
|
+
id: p.id,
|
|
457
|
+
solution: p.state.bestSolution,
|
|
458
|
+
score: p.state.bestEnergy,
|
|
459
|
+
lastUpdate: p.state.lastUpdate,
|
|
460
|
+
};
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
if (problemId) {
|
|
464
|
+
const problem = this.problems.find(p => p.id === problemId);
|
|
465
|
+
return problem ? formatSolution(problem) : null; // Le filtrage initial a déjà fait le travail
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Retourne un aperçu pour tous les problèmes
|
|
469
|
+
return this.problems.map(formatSolution).filter(s => s && s.solution);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Met à jour le payload d'un problème spécifique par son ID.
|
|
474
|
+
* @param {string} problemId - L'ID du problème à mettre à jour.
|
|
475
|
+
* @param {object} newPayload - Le nouvel objet payload qui remplacera l'ancien.
|
|
476
|
+
* @returns {boolean} - True si la mise à jour a réussi, false sinon.
|
|
477
|
+
*/
|
|
478
|
+
async updateProblemPayload(problemId, newPayload) {
|
|
479
|
+
const problem = this.problems.find(p => p.id === problemId);
|
|
480
|
+
if (!problem) {
|
|
481
|
+
console.error(`[ProblemManager] Impossible de mettre à jour : problème avec l'ID '${problemId}' non trouvé.`);
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
console.log(`[ProblemManager] Mise à jour du payload pour le problème '${problemId}'.`);
|
|
486
|
+
problem.payload = newPayload;
|
|
487
|
+
|
|
488
|
+
// Invalider l'état actuel car le problème a changé
|
|
489
|
+
problem.state.bestSolution = null;
|
|
490
|
+
problem.state.bestEnergy = "Infinity";
|
|
491
|
+
|
|
492
|
+
await this.store.set(`problem-state:${problem.id}`, problem.state);
|
|
493
|
+
return true;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
export { ProblemManager }; // Export the class for testing
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* @type {ProblemManager | null}
|
|
502
|
+
*/
|
|
503
|
+
let problemManagerInstance = null;
|
|
504
|
+
let managerPromise = null;
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Gets or creates the singleton instance of the ProblemManager.
|
|
508
|
+
* @param {object} [options] - The options for initialization.
|
|
509
|
+
* @param {string} [options.configPath='./problems.config.json'] - The path to the problems configuration file.
|
|
510
|
+
* @param {object} [options.config] - The problem configuration as an object.
|
|
511
|
+
* @param {IStore} [store] - The datastore instance.
|
|
512
|
+
* @returns {Promise<ProblemManager>} The singleton instance.
|
|
513
|
+
*/
|
|
514
|
+
export function getProblemManager(options = {}, store) {
|
|
515
|
+
const { configPath = './problems.config.json', config } = options;
|
|
516
|
+
|
|
517
|
+
const hasConfigChanged = problemManagerInstance && (
|
|
518
|
+
(config && problemManagerInstance.config !== config) ||
|
|
519
|
+
(configPath && problemManagerInstance.configPath !== configPath)
|
|
520
|
+
);
|
|
521
|
+
if (!managerPromise || hasConfigChanged || (problemManagerInstance && problemManagerInstance.store !== store)) {
|
|
522
|
+
managerPromise = ProblemManager.create({ configPath, config }, store).then(manager => {
|
|
523
|
+
problemManagerInstance = manager;
|
|
524
|
+
return manager;
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
return managerPromise;
|
|
528
|
+
}
|
|
529
|
+
export const problemManager = getProblemManager(); // This now exports a Promise
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* @internal
|
|
533
|
+
* For testing purposes only.
|
|
534
|
+
*/
|
|
535
|
+
export const __internal = {
|
|
536
|
+
resetManager: () => {
|
|
537
|
+
problemManagerInstance = null;
|
|
538
|
+
managerPromise = null;
|
|
539
|
+
}
|
|
523
540
|
};
|