@anonympins/fingerprint 0.6.7 → 0.7.1

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.
Files changed (63) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +3 -7
  3. package/composer.json +8 -1
  4. package/package.json +1 -1
  5. package/public/fingerprint-wordpress.zip +0 -0
  6. package/public/fp.wasm +0 -0
  7. package/src/js/build-client.js +9 -11
  8. package/src/js/dynamic-wasm.js +7 -7
  9. package/src/js/fingerprint.builder.js +37 -38
  10. package/src/js/fingerprint.client.js +236 -152
  11. package/src/js/fingerprint.client.obfuscated.js +1 -1
  12. package/src/js/fingerprint.js +517 -101
  13. package/src/js/fingerprint.utils.js +213 -213
  14. package/src/js/library.js +355 -384
  15. package/src/js/mongodb-store.js +1 -1
  16. package/src/js/optimization.worker.js +27 -27
  17. package/src/js/pow.solver.inline.js +206 -62
  18. package/src/js/pow.solver.js +235 -101
  19. package/src/js/pow.worker.js +48 -26
  20. package/src/js/redis-store.js +1 -1
  21. package/src/js/tests/fingerprint.client.init.test.js +146 -143
  22. package/src/js/tests/fingerprint.test.js +101 -40
  23. package/src/js/tests/ip-reputation.test.js +1 -0
  24. package/src/js/upow-model-task.js +14 -15
  25. package/src/php/AutoTuner.php +34 -34
  26. package/src/php/Challenge/ChallengeUtils.php +204 -32
  27. package/src/php/Config/SecurityProfiles.php +21 -0
  28. package/src/php/DirectFingerprint.php +190 -145
  29. package/src/php/FingerprintBuilder.php +29 -30
  30. package/src/php/FingerprintClient.php +24 -25
  31. package/src/php/FingerprintEngine.php +1836 -1880
  32. package/src/php/Ja3AnomalyDetector.php +258 -227
  33. package/src/php/Optimization/FunctionRegistry.php +7 -7
  34. package/src/php/Optimization/Optimization.php +24 -24
  35. package/src/php/Optimization/OptimizationOperators.php +28 -27
  36. package/src/php/Optimization/ProblemInitializers.php +52 -52
  37. package/src/php/ProblemManager.php +31 -34
  38. package/src/php/RequestContext.php +8 -1
  39. package/src/php/Store/IStore.php +7 -8
  40. package/src/php/Store/InMemoryStore.php +66 -66
  41. package/src/php/Store/MongoDbStore.php +5 -5
  42. package/src/php/Store/RedisStore.php +5 -5
  43. package/src/php/Store/StoreManager.php +35 -35
  44. package/src/php/Tests/ChallengeUtilsTest.php +453 -453
  45. package/src/php/Tests/DnsCircuitBreakerTest.php +105 -105
  46. package/src/php/Tests/FingerprintEngineTest.php +662 -693
  47. package/src/php/Tests/IpReputationTest.php +10 -0
  48. package/src/php/Tests/MaliciousPatternsTest.php +103 -103
  49. package/src/php/Tests/MetricsTest.php +97 -97
  50. package/src/php/Tests/ProblemManagerTest.php +376 -376
  51. package/src/php/Tests/QuicFingerprintTest.php +53 -53
  52. package/src/php/Tests/RequestUtilsTest.php +471 -471
  53. package/src/php/Tests/TLSClientHelloParserTest.php +177 -177
  54. package/src/php/Tests/config/ed25519_key.json +3 -3
  55. package/src/php/Utils/Env.php +49 -49
  56. package/src/php/Utils/MaliciousPatterns.php +74 -74
  57. package/src/php/Utils/MetricsManager.php +209 -209
  58. package/src/php/Utils/RequestUtils.php +236 -19
  59. package/src/php/Utils/TLSClientHelloParser.php +369 -212
  60. package/src/php/WordPress/WpDbStore.php +155 -0
  61. package/src/php/WordPress/fingerprint-wordpress.php +217 -0
  62. package/src/php/WordPress/package.php +123 -0
  63. package/src/php/bin/auto-tune.php +118 -118
package/src/js/library.js CHANGED
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * @file @/library.js
3
- * @description Une bibliothèque d'outils basés sur le principe fondamental de la dichotomie (division en deux).
4
- * Inclut des algorithmes pour les tableaux triés, des structures de données et des solveurs de problèmes conceptuels.
3
+ * @description A library of tools based on the fundamental principle of dichotomy (division in two).
4
+ * Includes algorithms for sorted arrays, data structures, and problem solvers.
5
5
  *
6
6
  * @template T
7
7
  * @callback Comparator
8
- * @param {T} element - L'élément du tableau.
9
- * @param {any} target - La valeur cible.
10
- * @returns {number} -1 si element < target, 0 si element == target, 1 si element > target.
8
+ * @param {T} element - Array element.
9
+ * @param {any} target - Target value.
10
+ * @returns {number} -1 if element < target, 0 if element == target, 1 if element > target.
11
11
  *
12
12
  */
13
13
 
@@ -16,25 +16,25 @@ import os from "node:os";
16
16
  import crypto from "node:crypto";
17
17
 
18
18
  /**
19
- * Génère un nombre flottant aléatoire entre 0 (inclus) et 1 (exclus).
19
+ * Generates a cryptographically secure random float between 0 (inclusive) and 1 (exclusive).
20
20
  * @returns {number}
21
21
  */
22
- const random = () => Math.random();
22
+ const random = () => {
23
+ return crypto.randomInt(0, 4294967296) / 4294967296;
24
+ };
23
25
 
24
26
  const Optimization = {
25
27
  // eslint-disable-line no-unused-vars
26
28
  /**
27
- * Trouve une bonne solution à un problème d'optimisation en utilisant le Recuit Simulé.
28
- * Cet algorithme est efficace pour trouver un optimum global dans un grand espace de recherche
29
- * avec de nombreux optima locaux (plusieurs "pics" ou "vallées").
30
- * @template TSolution - Le type de la solution (peut être un nombre, un tableau, un objet...).
31
- * @param {TSolution} initialSolution - Le point de départ de la recherche.
32
- * @param {function(TSolution): number} evaluator - Fonction qui évalue une solution. L'objectif est de minimiser ce score.
33
- * @param {function(TSolution): TSolution} neighbor - Fonction qui génère une solution "voisine" aléatoire.
34
- * @param {number} [initialTemperature=1000] - La température de départ.
35
- * @param {number} [coolingRate=0.995] - Le taux de refroidissement (proche de 1).
36
- * @param {number} [maxIterations=10000] - Le nombre total d'itérations.
37
- * @returns {{solution: TSolution, energy: number}} Le meilleur couple solution/score trouvé.
29
+ * Finds an optimized solution using Simulated Annealing.
30
+ * @template TSolution - Type of the solution (number, array, object...).
31
+ * @param {TSolution} initialSolution - Starting point for optimization.
32
+ * @param {function(TSolution): number} evaluator - Evaluates a solution (goal is to MINIMIZE score).
33
+ * @param {function(TSolution): TSolution} neighbor - Generates a random neighbor solution.
34
+ * @param {number} [initialTemperature=1000] - Initial temperature.
35
+ * @param {number} [coolingRate=0.995] - Cooling rate factor.
36
+ * @param {number} [maxIterations=10000] - Total iterations.
37
+ * @returns {{solution: TSolution, energy: number}} Best solution and corresponding energy score.
38
38
  */
39
39
  simulatedAnnealing(
40
40
  initialSolution,
@@ -56,24 +56,24 @@ const Optimization = {
56
56
  const newSolution = neighbor(currentSolution);
57
57
  const newEnergy = evaluator(newSolution);
58
58
 
59
- // Calcule la probabilité d'accepter une moins bonne solution.
59
+ // Calculate acceptance probability for a worse solution
60
60
  const acceptanceProbability = Math.exp(
61
61
  (currentEnergy - newEnergy) / temperature,
62
62
  );
63
63
 
64
- // Décide si on se déplace vers la nouvelle solution.
64
+ // Decide whether to transition to the candidate solution
65
65
  if (newEnergy < currentEnergy || random() < acceptanceProbability) {
66
66
  currentSolution = newSolution;
67
67
  currentEnergy = newEnergy;
68
68
  }
69
69
 
70
- // Met à jour la meilleure solution trouvée jusqu'à présent.
70
+ // Update global best found so far
71
71
  if (currentEnergy < bestEnergy) {
72
72
  bestSolution = currentSolution;
73
73
  bestEnergy = currentEnergy;
74
74
  }
75
75
 
76
- // Refroidit la température.
76
+ // Cool down temperature
77
77
  temperature *= coolingRate;
78
78
  }
79
79
 
@@ -81,21 +81,20 @@ const Optimization = {
81
81
  },
82
82
 
83
83
  /**
84
- * Résout un problème d'optimisation en utilisant un Algorithme Génétique.
85
- * Idéal pour les problèmes complexes où l'espace de recherche est vaste et non-linéaire.
86
- * @template TChromosome - Le type de la solution (un "chromosome").
87
- * @param {function(): TChromosome} createIndividual - Fonction pour créer un individu aléatoire.
88
- * @param {function(TChromosome): number} fitnessFunction - Évalue un individu. L'objectif est de MINIMISER ce score.
89
- * @param {function(TChromosome, TChromosome): TChromosome} crossover - Croise deux parents pour créer un enfant.
90
- * @param {function(TChromosome): TChromosome} mutate - Applique une mutation aléatoire à un individu.
91
- * @param {object} options - Options de l'algorithme.
92
- * @param {number} [options.populationSize=100] - Taille de la population.
93
- * @param {number} [options.generations=100] - Nombre de générations à simuler.
94
- * @param {number} [options.crossoverRate=0.8] - Probabilité de croisement.
95
- * @param {number} [options.mutationRate=0.1] - Probabilité de mutation.
96
- * @param {function} [options.selectionFunction] - Fonction de sélection des parents. Par défaut, un tournoi.
97
- * @param {boolean} [options.returnPopulation=false] - Si true, retourne la population finale au lieu du meilleur individu.
98
- * @returns {{solution: TChromosome, fitness: number}} Le meilleur individu trouvé.
84
+ * Solves an optimization problem using a Genetic Algorithm.
85
+ * @template TChromosome - Type of the chromosome.
86
+ * @param {function(): TChromosome} createIndividual - Creates a random individual.
87
+ * @param {function(TChromosome): number} fitnessFunction - Evaluates an individual (MINIMIZATION).
88
+ * @param {function(TChromosome, TChromosome): TChromosome} crossover - Crosses two parents.
89
+ * @param {function(TChromosome): TChromosome} mutate - Applies random mutation.
90
+ * @param {object} options - Algorithm options.
91
+ * @param {number} [options.populationSize=100] - Population size.
92
+ * @param {number} [options.generations=100] - Number of generations.
93
+ * @param {number} [options.crossoverRate=0.8] - Crossover rate.
94
+ * @param {number} [options.mutationRate=0.1] - Mutation rate.
95
+ * @param {function} [options.selectionFunction] - Parent selection function.
96
+ * @param {boolean} [options.returnPopulation=false] - Whether to return final population instead of single best.
97
+ * @returns {{solution: TChromosome, fitness: number}} Best individual found.
99
98
  */
100
99
  geneticAlgorithm(
101
100
  createIndividual,
@@ -115,33 +114,30 @@ const Optimization = {
115
114
  this.Operators.createTournamentSelection({ size: 5 });
116
115
  const returnPopulation = options.returnPopulation || false;
117
116
 
118
- // 1. Initialisation
119
- // La population est un tableau d'objets { chromosome, fitness }
120
- // La fitness est calculée une seule fois par individu.
117
+ // 1. Initialization
121
118
  let population = Array.from({ length: populationSize }, () => {
122
119
  const chromosome = createIndividual();
123
120
  return { chromosome, fitness: fitnessFunction(chromosome) };
124
121
  });
125
122
 
126
- // Trier la population initiale pour trouver le meilleur
123
+ // Sort initial population to find best
127
124
  population.sort((a, b) => a.fitness - b.fitness);
128
125
  let bestOverall = population[0];
129
126
 
130
- // 2. Boucle des générations
127
+ // 2. Generation loop
131
128
  for (let gen = 0; gen < generations; gen++) {
132
129
  const newPopulation = [];
133
130
 
134
- // Élitisme : le meilleur individu de la génération précédente est conservé.
135
- // Il est déjà à l'index 0 grâce au tri à la fin de la boucle précédente.
131
+ // Elitism: retain best individual from previous generation
136
132
  newPopulation.push(population[0]);
137
133
 
138
134
  while (newPopulation.length < populationSize) {
139
- // 3. Sélection
135
+ // 3. Selection
140
136
  const parent1 = selectionFunction(population);
141
137
  const parent2 = selectionFunction(population);
142
138
 
143
139
  let offspringChromosome;
144
- // 4. Croisement
140
+ // 4. Crossover
145
141
  if (random() < crossoverRate) {
146
142
  offspringChromosome = crossover(
147
143
  parent1.chromosome,
@@ -156,21 +152,20 @@ const Optimization = {
156
152
  offspringChromosome = mutate(offspringChromosome);
157
153
  }
158
154
 
159
- // S'assurer que les opérateurs ont bien retourné un individu
155
+ // Ensure operators returned a valid individual
160
156
  if (offspringChromosome) {
161
157
  newPopulation.push({
162
158
  chromosome: offspringChromosome,
163
159
  fitness: fitnessFunction(offspringChromosome),
164
160
  });
165
161
  } else {
166
- // Si le croisement/mutation échoue, on réinsère un parent pour garder la taille de la population
167
162
  newPopulation.push(parent1);
168
163
  }
169
164
  }
170
165
 
171
166
  population = newPopulation;
172
167
 
173
- // Trier la nouvelle population pour la prochaine génération (élitisme) et la mise à jour du meilleur
168
+ // Sort new population for elitism and update best overall
174
169
  population.sort((a, b) => a.fitness - b.fitness);
175
170
 
176
171
  if (population[0].fitness < bestOverall.fitness) {
@@ -186,13 +181,11 @@ const Optimization = {
186
181
  },
187
182
 
188
183
  /**
189
- * Exécute un solveur stochastique plusieurs fois et retourne le meilleur résultat.
190
- * C'est une méta-heuristique pour augmenter la probabilité de trouver un optimum global
191
- * en échange d'un temps de calcul plus long.
192
- * @param {function(): {solution: any, energy?: number, fitness?: number}} solverFunction - Une fonction qui, lorsqu'elle est appelée, exécute un algorithme d'optimisation et retourne un objet résultat.
193
- * @param {number} numCycles - Le nombre de fois où exécuter le solveur.
194
- * @param {boolean} [logProgress=false] - Si true, affiche le score de chaque cycle dans la console.
195
- * @returns {{bestResult: object, stats: {scores: Array<number>, average: number, stdDev: number}}} Le meilleur résultat et des statistiques sur les exécutions.
184
+ * Executes a stochastic solver multiple times and returns the best result.
185
+ * @param {function(): {solution: any, energy?: number, fitness?: number}} solverFunction - Solver function.
186
+ * @param {number} numCycles - Number of execution cycles.
187
+ * @param {boolean} [logProgress=false] - Log progress to console.
188
+ * @returns {{bestResult: object, stats: {scores: Array<number>, average: number, stdDev: number}}} Best result and run stats.
196
189
  */
197
190
  runMultiple(solverFunction, numCycles, logProgress = false) {
198
191
  let bestResult = null;
@@ -201,8 +194,7 @@ const Optimization = {
201
194
  for (let i = 0; i < numCycles; i++) {
202
195
  const currentResult = solverFunction();
203
196
 
204
- // Gère les résultats du Recuit Simulé (energy) et des Algorithmes Génétiques (fitness).
205
- // On suppose que pour les deux, un score plus bas est meilleur.
197
+ // Handle simulated annealing (energy) and genetic algorithm (fitness) results (lower is better)
206
198
  const currentScore =
207
199
  currentResult.energy !== undefined
208
200
  ? currentResult.energy
@@ -211,7 +203,7 @@ const Optimization = {
211
203
 
212
204
  if (logProgress) {
213
205
  console.log(
214
- ` -> Cycle ${i + 1}/${numCycles}: Score trouvé = ${currentScore.toFixed(2)}`,
206
+ ` -> Cycle ${i + 1}/${numCycles}: Score found = ${currentScore.toFixed(2)}`,
215
207
  );
216
208
  }
217
209
 
@@ -226,7 +218,7 @@ const Optimization = {
226
218
  }
227
219
  }
228
220
 
229
- // Calcul des statistiques
221
+ // Statistical calculations
230
222
  const sum = allScores.reduce((a, b) => a + b, 0);
231
223
  const average = sum / numCycles;
232
224
  const variance =
@@ -244,16 +236,15 @@ const Optimization = {
244
236
  },
245
237
 
246
238
  /**
247
- * Trouve un minimum local d'une fonction en utilisant l'algorithme de Descente de Gradient.
248
- * Nécessite que la fonction soit différentiable et que son gradient soit connu.
249
- * @template TSolution - Le type de la solution (nombre ou tableau de nombres).
250
- * @param {TSolution} initialSolution - Le point de départ.
251
- * @param {function(TSolution): TSolution} gradientFunction - Fonction qui calcule le gradient au point donné.
252
- * @param {object} options - Options de l'algorithme.
253
- * @param {number} [options.learningRate=0.01] - Le "pas" de la descente.
254
- * @param {number} [options.maxIterations=1000] - Nombre d'itérations.
255
- * @param {number} [options.tolerance=1e-6] - Seuil pour arrêter si la solution ne change plus beaucoup.
256
- * @returns {TSolution} La solution (minimum local) trouvée.
239
+ * Finds a local minimum of a differentiable function using Gradient Descent.
240
+ * @template TSolution - Solution type (number or array of numbers).
241
+ * @param {TSolution} initialSolution - Starting point.
242
+ * @param {function(TSolution): TSolution} gradientFunction - Computes gradient at given point.
243
+ * @param {object} options - Algorithm options.
244
+ * @param {number} [options.learningRate=0.01] - Step size.
245
+ * @param {number} [options.maxIterations=1000] - Max iterations.
246
+ * @param {number} [options.tolerance=1e-6] - Stopping threshold.
247
+ * @returns {TSolution} Local minimum found.
257
248
  */
258
249
  gradientDescent(initialSolution, gradientFunction, options = {}) {
259
250
  const {
@@ -280,7 +271,7 @@ const Optimization = {
280
271
  );
281
272
  if (change < tolerance) break;
282
273
  } else {
283
- // Cas d'une seule variable (nombre)
274
+ // Single variable scalar case
284
275
  const prevSolution = currentSolution;
285
276
  currentSolution -= learningRate * gradient;
286
277
  if (Math.abs(prevSolution - currentSolution) < tolerance) break;
@@ -290,15 +281,15 @@ const Optimization = {
290
281
  },
291
282
 
292
283
  /**
293
- * Exécute un solveur stochastique plusieurs fois en parallèle en utilisant un pool de workers pour éviter de surcharger le système.
294
- * @param {string} solverName - Le nom de la fonction solveur à appeler dans `Optimization.Operators`.
295
- * @param {Array<any>} baseSolverArgs - Les arguments de base à passer au solveur (sans les données aléatoires qui seront générées par worker).
296
- * @param {number} numCycles - Le nombre total de cycles à exécuter.
297
- * @param {boolean} [logProgress=false] - Si true, affiche la progression dans la console.
298
- * @param {object} [options={}] - Options pour la parallélisation.
299
- * @param {number} [options.concurrency] - Le nombre de workers à utiliser en parallèle. Par défaut, le nombre de cœurs CPU.
300
- * @param {function(number): Array<any>} [options.workerDataGenerator] - Une fonction qui, pour chaque cycle (index), génère les arguments spécifiques à passer au solveur. Si non fournie, `baseSolverArgs` est utilisé tel quel.
301
- * @returns {Promise<{bestResult: object, stats: {scores: Array<number>, average: number, stdDev: number}}>} Le meilleur résultat et des statistiques.
284
+ * Executes a stochastic solver multiple times in parallel using a worker pool.
285
+ * @param {string} solverName - Name of the solver function in `Optimization.Operators`.
286
+ * @param {Array<any>} baseSolverArgs - Base arguments for solver.
287
+ * @param {number} numCycles - Total cycles to run.
288
+ * @param {boolean} [logProgress=false] - Log progress.
289
+ * @param {object} [options={}] - Concurrency options.
290
+ * @param {number} [options.concurrency] - Worker concurrency count (defaults to CPU count).
291
+ * @param {function(number): Array<any>} [options.workerDataGenerator] - Dynamic argument generator per task index.
292
+ * @returns {Promise<{bestResult: object, stats: {scores: Array<number>, average: number, stdDev: number}}>} Best result and stats.
302
293
  */
303
294
  async runMultipleParallel(
304
295
  solverName,
@@ -312,7 +303,7 @@ const Optimization = {
312
303
 
313
304
  if (logProgress) {
314
305
  console.log(
315
- ` (Utilisation d'un pool de ${concurrency} workers pour ${numCycles} cycles avec le solveur ${solverName})`,
306
+ ` (Using a pool of ${concurrency} workers for ${numCycles} cycles with solver ${solverName})`,
316
307
  );
317
308
  }
318
309
 
@@ -333,15 +324,14 @@ const Optimization = {
333
324
  };
334
325
 
335
326
  const result = await new Promise(async (resolve, reject) => {
336
- // Le chemin du worker doit être absolu ou relatif au fichier appelant.
337
- // On utilise import.meta.url pour résoudre le chemin de manière fiable.
327
+ // Resolve worker script path relative to current module
338
328
  const worker = new Worker(new URL('./optimization.worker.js', import.meta.url), { workerData });
339
329
  worker.on("message", resolve);
340
330
  worker.on("error", reject);
341
331
  worker.on("exit", (code) => {
342
332
  if (code !== 0)
343
333
  reject(
344
- new Error(`Worker ${workerId} a terminé avec le code ${code}`),
334
+ new Error(`Worker ${workerId} exited with code ${code}`),
345
335
  );
346
336
  });
347
337
  });
@@ -363,7 +353,7 @@ const Optimization = {
363
353
  );
364
354
  await Promise.all(workerPromises);
365
355
 
366
- // Le reste de la logique est identique à `runMultiple`
356
+ // Remainder follows runMultiple logic
367
357
  let bestResult = null;
368
358
  const allScores = [];
369
359
  allResults.forEach((result) => {
@@ -395,30 +385,30 @@ const Optimization = {
395
385
  };
396
386
 
397
387
  /**
398
- * Détermine si la solution A domine la solution B en multi-objectifs (problème de minimisation).
388
+ * Determines whether solution A Pareto-dominates solution B (minimization problem).
399
389
  * @private
400
- * @param {number[]} objectivesA - Tableau des scores des objectifs pour la solution A.
401
- * @param {number[]} objectivesB - Tableau des scores des objectifs pour la solution B.
402
- * @returns {boolean} - True si A domine B.
390
+ * @param {number[]} objectivesA - Objective scores for solution A.
391
+ * @param {number[]} objectivesB - Objective scores for solution B.
392
+ * @returns {boolean} - True if A dominates B.
403
393
  */
404
394
  function paretoDominates(objectivesA, objectivesB) {
405
395
  let aIsBetterInOne = false;
406
396
  for (let i = 0; i < objectivesA.length; i++) {
407
397
  if (objectivesA[i] > objectivesB[i]) {
408
- return false; // A est pire sur au moins un objectif, donc ne domine pas.
398
+ return false; // A is worse on at least one objective
409
399
  }
410
400
  if (objectivesA[i] < objectivesB[i]) {
411
- aIsBetterInOne = true; // A est strictement meilleur sur au moins un objectif.
401
+ aIsBetterInOne = true; // A is strictly better on at least one objective
412
402
  }
413
403
  }
414
- return aIsBetterInOne; // A domine B si elle n'est jamais pire et au moins une fois meilleure.
404
+ return aIsBetterInOne;
415
405
  }
416
406
 
417
407
  /**
418
- * Trie une population en fronts de Pareto non-dominés (inspiré de NSGA-II).
408
+ * Sorts a population into non-dominated Pareto fronts (inspired by NSGA-II).
419
409
  * @private
420
- * @param {Array<{individual: any, objectives: number[]}>} populationWithObjectives - La population à trier.
421
- * @returns {Array<Array<{individual: any, objectives: number[]}>>} - Un tableau de fronts, où le premier est le meilleur.
410
+ * @param {Array<{individual: any, objectives: number[]}>} populationWithObjectives - Population to sort.
411
+ * @returns {Array<Array<{individual: any, objectives: number[]}>>} - Array of fronts, first being the best.
422
412
  */
423
413
  function nonDominatedSort(populationWithObjectives) {
424
414
  const fronts = [[]];
@@ -460,9 +450,9 @@ function nonDominatedSort(populationWithObjectives) {
460
450
  }
461
451
 
462
452
  /**
463
- * Calcule la distance de promiscuité (crowding distance) pour un front, afin de préserver la diversité.
453
+ * Calculates crowding distance for a front to preserve diversity.
464
454
  * @private
465
- * @param {Array<{individual: any, objectives: number[]}>} front - Le front de Pareto.
455
+ * @param {Array<{individual: any, objectives: number[]}>} front - Pareto front.
466
456
  */
467
457
  function calculateCrowdingDistance(front) {
468
458
  if (front.length === 0) return;
@@ -474,7 +464,7 @@ function calculateCrowdingDistance(front) {
474
464
  const minObj = front[0].objectives[i];
475
465
  const maxObj = front[front.length - 1].objectives[i];
476
466
 
477
- // Les solutions aux extrémités sont cruciales, on leur donne une distance infinie.
467
+ // Boundary solutions receive infinite distance to encourage spread
478
468
  front[0].crowdingDistance = Infinity;
479
469
  front[front.length - 1].crowdingDistance = Infinity;
480
470
 
@@ -489,13 +479,13 @@ function calculateCrowdingDistance(front) {
489
479
  }
490
480
 
491
481
  /**
492
- * Algorithme génétique multi-objectifs (inspiré de NSGA-II) pour trouver un front de Pareto.
493
- * @param {function(): any} createIndividual - Fonction qui crée un individu aléatoire.
494
- * @param {function(any): number[]} fitnessFunction - Fonction qui évalue un individu et retourne un tableau d'objectifs à MINIMISER.
495
- * @param {function(any, any): any} crossover - Fonction de croisement.
496
- * @param {function(any): any} mutate - Fonction de mutation.
497
- * @param {object} options - Options de l'algorithme.
498
- * @returns {Array<{solution: any, objectives: number[]}>} Le premier front de Pareto (l'ensemble des meilleures solutions de compromis).
482
+ * Multi-objective genetic algorithm (inspired by NSGA-II) to find a Pareto front.
483
+ * @param {function(): any} createIndividual - Creates a random individual.
484
+ * @param {function(any): number[]} fitnessFunction - Evaluates individual and returns array of objectives to MINIMIZE.
485
+ * @param {function(any, any): any} crossover - Crossover function.
486
+ * @param {function(any): any} mutate - Mutation function.
487
+ * @param {object} options - Algorithm options.
488
+ * @returns {Array<{solution: any, objectives: number[]}>} Non-dominated Pareto front.
499
489
  */
500
490
  Optimization.geneticAlgorithmMultiObjective = function (
501
491
  createIndividual,
@@ -505,48 +495,47 @@ Optimization.geneticAlgorithmMultiObjective = function (
505
495
  options = {},
506
496
  ) {
507
497
  const {
508
- generations = 150, // Augmenté pour une meilleure convergence
509
- populationSize = 60, // Augmenté pour plus de diversité
498
+ generations = 150,
499
+ populationSize = 60,
510
500
  mutationRate = 0.1,
511
501
  currentConfig = null,
512
502
  } = options;
513
503
 
514
504
  let population = Array.from({ length: populationSize }, () => ({
515
505
  individual: createIndividual(),
516
- })); // createIndividual doit maintenant utiliser currentConfig
506
+ }));
517
507
  population.forEach((p) => (p.objectives = fitnessFunction(p.individual)));
518
508
 
519
509
  for (let gen = 0; gen < generations; gen++) {
520
- // 1. Créer une population d'enfants
510
+ // 1. Generate offspring
521
511
  const offspring = [];
522
512
  for (let i = 0; i < populationSize; i++) {
523
- // Sélection simple pour l'exemple
524
513
  const parent1 = population[crypto.randomInt(0, population.length)];
525
514
  const parent2 = population[crypto.randomInt(0, population.length)];
526
515
  let childIndividual = crossover(parent1.individual, parent2.individual);
527
516
  if (random() < mutationRate) {
528
- childIndividual = mutate(childIndividual, currentConfig); // mutate doit maintenant utiliser currentConfig
517
+ childIndividual = mutate(childIndividual, currentConfig);
529
518
  }
530
519
  const child = { individual: childIndividual };
531
520
  child.objectives = fitnessFunction(child.individual);
532
521
  offspring.push(child);
533
522
  }
534
523
 
535
- // 2. Combiner parents et enfants
524
+ // 2. Combine parent and offspring populations
536
525
  const combinedPopulation = [...population, ...offspring];
537
526
 
538
- // 3. Trier la population combinée en fronts
527
+ // 3. Sort into Pareto fronts
539
528
  const fronts = nonDominatedSort(combinedPopulation);
540
529
 
541
- // 4. Construire la nouvelle population
530
+ // 4. Build next generation population
542
531
  const newPopulation = [];
543
532
  for (const front of fronts) {
544
533
  if (newPopulation.length + front.length <= populationSize) {
545
534
  newPopulation.push(...front);
546
535
  } else {
547
- // Si le front est trop grand, on utilise la distance de promiscuité pour choisir les individus les plus diversifiés.
536
+ // Use crowding distance truncation if front overflows population size
548
537
  calculateCrowdingDistance(front);
549
- front.sort((a, b) => b.crowdingDistance - a.crowdingDistance); // Trier par distance décroissante
538
+ front.sort((a, b) => b.crowdingDistance - a.crowdingDistance);
550
539
  const remaining = populationSize - newPopulation.length;
551
540
  newPopulation.push(...front.slice(0, remaining));
552
541
  break;
@@ -555,11 +544,11 @@ Optimization.geneticAlgorithmMultiObjective = function (
555
544
  population = newPopulation;
556
545
  }
557
546
 
558
- // Retourner le premier front de la population finale
547
+ // Return the first front of the final population
559
548
  const finalFronts = nonDominatedSort(population);
560
549
  const bestFront = finalFronts.length > 0 ? finalFronts[0] : [];
561
550
 
562
- // Filtrer le front pour ne garder que les solutions avec des objectifs uniques
551
+ // Deduplicate solutions with identical objectives
563
552
  const uniqueSolutionsMap = new Map();
564
553
  for (const p of bestFront) {
565
554
  const key = JSON.stringify(p.objectives);
@@ -574,68 +563,68 @@ Optimization.geneticAlgorithmMultiObjective = function (
574
563
  };
575
564
 
576
565
  /**
577
- * Utilitaires pour les problèmes d'optimisation.
566
+ * Optimization utilities.
578
567
  */
579
568
  Optimization.Utils = {
580
- /** Calcule la distance euclidienne entre deux points (villes). */
569
+ /** Calculates Euclidean distance between two points/cities. */
581
570
  distance: (city1, city2) => Math.sqrt(Math.pow(city1.x - city2.x, 2) + Math.pow(city1.y - city2.y, 2)),
582
571
 
583
- /** Évalue la distance totale d'un chemin TSP donné. */
572
+ /** Evaluates the total round-trip distance of a TSP path. */
584
573
  evaluatePathDistance: (cities, path) => {
585
574
  let totalDistance = 0;
586
575
  for (let i = 0; i < path.length - 1; i++) {
587
576
  totalDistance += Optimization.Utils.distance(cities[path[i]], cities[path[i + 1]]);
588
577
  }
589
- totalDistance += Optimization.Utils.distance(cities[path[path.length - 1]], cities[path[0]]); // Retour au départ
578
+ totalDistance += Optimization.Utils.distance(cities[path[path.length - 1]], cities[path[0]]); // Return to start
590
579
  return totalDistance;
591
580
  }
592
581
  };
593
582
 
594
583
  /**
595
- * Algorithme d'optimisation CMA-ES (Covariance Matrix Adaptation Evolution Strategy).
596
- * C'est un algorithme de pointe pour l'optimisation en boîte noire de fonctions non-linéaires et non-convexes.
597
- * Il est particulièrement efficace pour les problèmes avec des variables continues.
598
- * @param {function(Array<number>): number} fitnessFunction - La fonction à MINIMISER.
599
- * @param {Array<number>} initialSolution - Le point de départ de la recherche (un vecteur de nombres).
600
- * @param {number} initialStepSize - La taille de pas initiale (sigma).
601
- * @param {object} [options={}] - Options de l'algorithme.
602
- * @param {number} [options.maxGenerations=100] - Nombre maximum de générations.
603
- * @param {number} [options.populationSize] - Taille de la population (lambda). Calculée par défaut si non fournie.
604
- * @param {number} [options.tolerance=1e-6] - Seuil de tolérance pour l'arrêt précoce.
605
- * @returns {{solution: Array<number>, fitness: number}} La meilleure solution trouvée.
584
+ * CMA-ES (Covariance Matrix Adaptation Evolution Strategy) optimization algorithm.
585
+ * State-of-the-art evolutionary algorithm for black-box optimization of non-linear, non-convex functions.
586
+ * Particularly effective for problems involving continuous real-valued variables.
587
+ * @param {function(Array<number>): number} fitnessFunction - The objective function to MINIMIZE.
588
+ * @param {Array<number>} initialSolution - Starting point for the search (numeric vector).
589
+ * @param {number} initialStepSize - Initial search step size (sigma).
590
+ * @param {object} [options={}] - Algorithm options.
591
+ * @param {number} [options.maxGenerations=100] - Maximum number of generations.
592
+ * @param {number} [options.populationSize] - Population size (lambda). Automatically computed if omitted.
593
+ * @param {number} [options.tolerance=1e-6] - Tolerance threshold for early stopping.
594
+ * @returns {{solution: Array<number>, fitness: number}} Best solution found and its fitness score.
606
595
  */
607
596
  Optimization.cmaes = function(fitnessFunction, initialSolution, initialStepSize, options = {}) {
608
- const n = initialSolution.length; // Dimension du problème
597
+ const n = initialSolution.length; // Problem dimension
609
598
 
610
- // --- Paramètres de l'algorithme (stratégie) ---
599
+ // --- Algorithm strategy parameters ---
611
600
  const { maxGenerations = 100, tolerance = 1e-6 } = options;
612
601
  const populationSize = options.populationSize || (4 + Math.floor(3 * Math.log(n))); // Lambda
613
- const mu = Math.floor(populationSize / 2); // Nombre de parents pour la recombinaison
602
+ const mu = Math.floor(populationSize / 2); // Parent count for recombination
614
603
 
615
- // Poids de recombinaison
604
+ // Recombination weights
616
605
  let weights = Array.from({ length: mu }, (_, i) => Math.log(mu + 0.5) - Math.log(i + 1));
617
606
  const sumWeights = weights.reduce((s, w) => s + w, 0);
618
607
  weights = weights.map(w => w / sumWeights);
619
608
  const muEff = 1 / weights.reduce((s, w) => s + w * w, 0);
620
609
 
621
- // Paramètres d'adaptation
610
+ // Adaptation parameters
622
611
  const cc = (4 + muEff / n) / (n + 4 + 2 * muEff / n);
623
612
  const cs = (muEff + 2) / (n + muEff + 5);
624
613
  const c1 = 2 / (Math.pow(n + 1.3, 2) + muEff);
625
614
  const cmu = Math.min(1 - c1, 2 * (muEff - 2 + 1 / muEff) / (Math.pow(n + 2, 2) + muEff));
626
615
  const damps = 1 + 2 * Math.max(0, Math.sqrt((muEff - 1) / (n + 1)) - 1) + cs;
627
616
 
628
- // --- Variables d'état dynamiques ---
629
- let mean = [...initialSolution]; // Le centre de la distribution de recherche
617
+ // --- Dynamic state variables ---
618
+ let mean = [...initialSolution]; // Distribution mean / search center
630
619
  let stepSize = initialStepSize; // Sigma
631
- let C = Array.from({ length: n }, (_, i) => Array.from({ length: n }, (_, j) => (i === j ? 1 : 0))); // Matrice de covariance
632
- let pc = Array(n).fill(0); // Chemin d'évolution pour C
633
- let ps = Array(n).fill(0); // Chemin d'évolution pour sigma
620
+ let C = Array.from({ length: n }, (_, i) => Array.from({ length: n }, (_, j) => (i === j ? 1 : 0))); // Covariance matrix
621
+ let pc = Array(n).fill(0); // Evolution path for C
622
+ let ps = Array(n).fill(0); // Evolution path for sigma
634
623
 
635
624
  let bestFitness = Infinity;
636
625
  let bestSolution = null;
637
626
 
638
- // Fonction pour la décomposition de Cholesky (simplifiée, pour matrice symétrique définie positive)
627
+ // Cholesky decomposition helper (simplified for symmetric positive-definite matrices)
639
628
  function cholesky(A) {
640
629
  const L = Array.from({ length: n }, () => Array(n).fill(0));
641
630
  for (let i = 0; i < n; i++) {
@@ -646,7 +635,7 @@ Optimization.cmaes = function(fitnessFunction, initialSolution, initialStepSize,
646
635
  }
647
636
  if (i === j) {
648
637
  const val = A[i][i] - sum;
649
- if (val < 0) return null; // Non définie positive
638
+ if (val < 0) return null; // Not positive definite
650
639
  L[i][j] = Math.sqrt(val);
651
640
  } else {
652
641
  if (L[j][j] === 0) return null;
@@ -658,18 +647,18 @@ Optimization.cmaes = function(fitnessFunction, initialSolution, initialStepSize,
658
647
  }
659
648
 
660
649
  for (let gen = 0; gen < maxGenerations; gen++) {
661
- // 1. Échantillonnage de la nouvelle population
650
+ // 1. Sample new population
662
651
  const population = [];
663
- const arx = []; // Vecteurs de recherche
652
+ const arx = []; // Search vectors
664
653
  const L = cholesky(C);
665
654
  if (!L) {
666
- console.warn("[CMA-ES] La matrice de covariance n'est plus définie positive. Arrêt.");
655
+ console.warn("[CMA-ES] Covariance matrix is no longer positive definite. Stopping.");
667
656
  break;
668
657
  }
669
658
 
670
659
  for (let i = 0; i < populationSize; i++) {
671
- const z = Array.from({ length: n }, () => random() * 2 - 1); // Vecteur normal standard
672
- const y = Array(n).fill(0); // z transformé par L
660
+ const z = Array.from({ length: n }, () => random() * 2 - 1); // Standard normal approximation
661
+ const y = Array(n).fill(0); // z transformed by L
673
662
  for (let r = 0; r < n; r++) {
674
663
  for (let c = 0; c < n; c++) {
675
664
  y[r] += L[r][c] * z[c];
@@ -680,7 +669,7 @@ Optimization.cmaes = function(fitnessFunction, initialSolution, initialStepSize,
680
669
  population.push({ individual, fitness: fitnessFunction(individual) });
681
670
  }
682
671
 
683
- // 2. Trier et sélectionner les meilleurs
672
+ // 2. Sort and select elite parents
684
673
  population.sort((a, b) => a.fitness - b.fitness);
685
674
  const parents = population.slice(0, mu);
686
675
 
@@ -689,7 +678,7 @@ Optimization.cmaes = function(fitnessFunction, initialSolution, initialStepSize,
689
678
  bestSolution = parents[0].individual;
690
679
  }
691
680
 
692
- // 3. Mise à jour des variables d'état
681
+ // 3. Update distribution mean
693
682
  const oldMean = [...mean];
694
683
  const y_w = Array(n).fill(0);
695
684
  for (let j = 0; j < n; j++) {
@@ -700,15 +689,15 @@ Optimization.cmaes = function(fitnessFunction, initialSolution, initialStepSize,
700
689
  }
701
690
  mean = oldMean.map((m, i) => m + stepSize * y_w[i]);
702
691
 
703
- // 4. Adaptation des chemins d'évolution
704
- const C_inv_sqrt = cholesky(C); // Simplification, devrait être l'inverse de la racine
692
+ // 4. Adapt evolution paths
693
+ const C_inv_sqrt = cholesky(C); // Simplification, ideally inverse square root
705
694
  const C_inv_sqrt_y_w = y_w; // Approximation
706
695
  ps = ps.map((p, i) => (1 - cs) * p + Math.sqrt(cs * (2 - cs) * muEff) * C_inv_sqrt_y_w[i]);
707
696
 
708
697
  const hsig = Math.sqrt(ps.reduce((s, v) => s + v*v, 0)) / (1 - Math.pow(1 - cs, 2 * (gen + 1))) / n < 1.4 + 2 / (n + 1);
709
698
  pc = pc.map((p, i) => (1 - cc) * p + (hsig ? Math.sqrt(cc * (2 - cc) * muEff) * y_w[i] : 0));
710
699
 
711
- // 5. Adaptation de la matrice de covariance C
700
+ // 5. Adapt covariance matrix C
712
701
  let rankOneUpdate = Array.from({ length: n }, (_, i) => Array.from({ length: n }, (_, j) => c1 * pc[i] * pc[j]));
713
702
  let rankMuUpdate = Array.from({ length: n }, () => Array(n).fill(0));
714
703
  for (let k = 0; k < mu; k++) {
@@ -721,7 +710,7 @@ Optimization.cmaes = function(fitnessFunction, initialSolution, initialStepSize,
721
710
  }
722
711
  C = C.map((row, i) => row.map((val, j) => (1 - c1 - cmu) * val + rankOneUpdate[i][j] + rankMuUpdate[i][j]));
723
712
 
724
- // 6. Adaptation de la taille de pas (sigma)
713
+ // 6. Adapt step size (sigma)
725
714
  stepSize *= Math.exp((cs / damps) * (Math.sqrt(ps.reduce((s, v) => s + v*v, 0)) / Math.sqrt(n) - 1));
726
715
  }
727
716
 
@@ -730,16 +719,16 @@ Optimization.cmaes = function(fitnessFunction, initialSolution, initialStepSize,
730
719
 
731
720
  /**
732
721
  * @namespace Optimization.Operators
733
- * @description Une bibliothèque de "fabriques d'évaluateurs" pour des problèmes d'optimisation complexes,
734
- * souvent multidimensionnels, à utiliser avec les algorithmes de `Optimization` (Recuit Simulé, Algorithmes Génétiques, etc.).
722
+ * @description Evaluator factories for complex, multi-dimensional optimization problems
723
+ * designed for use with Optimization solvers (Simulated Annealing, Genetic Algorithms, etc.).
735
724
  */
736
- Optimization.Operators = {}; // Création du namespace
725
+ Optimization.Operators = {}; // Namespace creation
737
726
 
738
727
  /**
739
- * Crée une fonction de sélection par tournoi pour un algorithme génétique.
740
- * @param {object} [options] - Options pour le tournoi.
741
- * @param {number} [options.size=5] - Le nombre de participants par tournoi.
742
- * @returns {function(Array<{chromosome: any, fitness: number}>): {chromosome: any, fitness: number}} Une fonction de sélection.
728
+ * Creates a tournament selection operator for genetic algorithms.
729
+ * @param {object} [options] - Tournament options.
730
+ * @param {number} [options.size=5] - Number of candidates per tournament.
731
+ * @returns {function(Array<{chromosome: any, fitness: number}>): {chromosome: any, fitness: number}} Tournament selection operator.
743
732
  */
744
733
  Optimization.Operators.createTournamentSelection = (options = {}) => {
745
734
  const tournamentSize = options.size || 5;
@@ -754,8 +743,8 @@ Optimization.Operators.createTournamentSelection = (options = {}) => {
754
743
  best = individual;
755
744
  }
756
745
  }
757
- // Retourne le meilleur trouvé. Dans le pire des cas (tous les scores sont Infinity),
758
- // on retourne le premier candidat sélectionné au lieu de null.
746
+ // Returns the best candidate. In degenerate edge cases (all fitness scores are Infinity),
747
+ // fallback to a random candidate instead of returning null.
759
748
  if (!best) {
760
749
  return population[crypto.randomInt(0, population.length)];
761
750
  }
@@ -764,12 +753,12 @@ Optimization.Operators.createTournamentSelection = (options = {}) => {
764
753
  };
765
754
 
766
755
  /**
767
- * Crée une matrice de covariance à partir de coefficients de corrélation déclarés.
768
- * C'est une manière plus intuitive de définir les relations de risque entre les actifs.
769
- * @param {object} config - L'objet de configuration.
770
- * @param {Array<{name: string, volatility: number}>} config.assets - La liste des actifs avec leur volatilité.
771
- * @param {Array<{assets: [string, string], correlation: number}>} config.correlations - Une liste de relations de corrélation.
772
- * @returns {Array<Array<number>>} La matrice de covariance calculée.
756
+ * Creates a covariance matrix from pairwise asset correlations.
757
+ * Provides an intuitive representation for cross-asset risk modeling.
758
+ * @param {object} config - Configuration object.
759
+ * @param {Array<{name: string, volatility: number}>} config.assets - Asset definitions with volatilities.
760
+ * @param {Array<{assets: [string, string], correlation: number}>} config.correlations - Pairwise correlation entries.
761
+ * @returns {Array<Array<number>>} Resulting covariance matrix.
773
762
  */
774
763
  Optimization.Operators.createCovarianceMatrixFromCorrelations = ({
775
764
  assets,
@@ -778,24 +767,24 @@ Optimization.Operators.createCovarianceMatrixFromCorrelations = ({
778
767
  const n = assets.length;
779
768
  const matrix = Array.from({ length: n }, () => Array(n).fill(0));
780
769
 
781
- // Créer un map pour un accès rapide aux infos des actifs par leur nom.
770
+ // Fast lookup map for asset details by name
782
771
  const assetInfo = new Map();
783
772
  assets.forEach((asset, index) => {
784
773
  assetInfo.set(asset.name, { index, volatility: asset.volatility });
785
774
  });
786
775
 
787
- // 1. Remplir la diagonale avec les variances (volatilité^2)
776
+ // 1. Fill diagonal with individual variances (volatility^2)
788
777
  for (let i = 0; i < n; i++) {
789
778
  const variance = Math.pow(assets[i].volatility, 2);
790
779
  matrix[i][i] = variance;
791
780
  }
792
781
 
793
- // 2. Remplir les autres cellules avec les covariances calculées
782
+ // 2. Fill off-diagonal cells with computed covariances
794
783
  for (const corr of correlations) {
795
784
  const [nameA, nameB] = corr.assets;
796
785
  if (!assetInfo.has(nameA) || !assetInfo.has(nameB)) {
797
786
  console.warn(
798
- `Avertissement: L'un des actifs [${nameA}, ${nameB}] n'a pas été trouvé. La corrélation est ignorée.`,
787
+ `Warning: One of assets [${nameA}, ${nameB}] was not found. Correlation ignored.`,
799
788
  );
800
789
  continue;
801
790
  }
@@ -807,13 +796,13 @@ Optimization.Operators.createCovarianceMatrixFromCorrelations = ({
807
796
  const covariance = corr.correlation * infoA.volatility * infoB.volatility;
808
797
 
809
798
  matrix[infoA.index][infoB.index] = covariance;
810
- matrix[infoB.index][infoA.index] = covariance; // La matrice est symétrique
799
+ matrix[infoB.index][infoA.index] = covariance; // Symmetric matrix
811
800
  }
812
801
 
813
802
  return matrix;
814
803
  };
815
804
 
816
- // Le "déséquilibre" est la différence absolue entre l'offre et la demande. On veut le minimiser.
805
+ // Market imbalance is the absolute delta between supply and demand (goal is minimization)
817
806
  Optimization.Operators.createMarketEquilibriumEvaluator = (
818
807
  demandModel,
819
808
  supplyModel,
@@ -825,26 +814,26 @@ Optimization.Operators.createMarketEquilibriumEvaluator = (
825
814
  };
826
815
  };
827
816
 
828
- // Un "individu" est un tableau de 3 poids (ex: [0.5, 0.2, 0.3]) qui doivent sommer à 1.
829
817
  /**
830
- * Crée une fonction de fitness pour l'optimisation de portefeuille.
831
- * @param {object} config - L'objet de configuration.
832
- * @param {Array<{name: string, expectedReturn: number, volatility: number}>} config.assets - Les actifs disponibles.
833
- * @param {number} config.maxVolatility - La contrainte de volatilité maximale du portefeuille.
834
- * @param {Array<Array<number>>} [config.covarianceMatrix] - Matrice de covariance pour un calcul de risque précis.
835
- * @returns {function(Array<number>): number} Une fonction de fitness qui évalue un portefeuille (tableau de poids).
818
+ * Creates a fitness function for portfolio allocation.
819
+ * An individual is a weight array (e.g. [0.5, 0.2, 0.3]) normalized to sum to 1.
820
+ * @param {object} config - Configuration object.
821
+ * @param {Array<{name: string, expectedReturn: number, volatility: number}>} config.assets - Target assets.
822
+ * @param {number} config.maxVolatility - Maximum portfolio volatility threshold constraint.
823
+ * @param {Array<Array<number>>} [config.covarianceMatrix] - Covariance matrix for exact variance evaluation.
824
+ * @returns {function(Array<number>): number} Fitness evaluator returning negative return with risk penalty.
836
825
  */
837
826
  Optimization.Operators.createPortfolioAllocator = ({
838
827
  assets,
839
828
  maxVolatility,
840
829
  covarianceMatrix,
841
830
  }) => {
842
- // La fonction de fitness évalue un portefeuille (un tableau de poids).
843
- // L'objectif est de MINIMISER le score, donc on minimise le rendement NÉGATIF.
831
+ // Evaluates a portfolio weight vector.
832
+ // Since solvers minimize, we minimize NEGATIVE expected return.
844
833
  return function portfolioFitness(weights) {
845
- // Normaliser les poids pour qu'ils somment à 1
834
+ // Normalize weights to ensure total equals 1.0
846
835
  const totalWeight = weights.reduce((sum, w) => sum + w, 0);
847
- if (totalWeight === 0) return Infinity; // Éviter la division par zéro, score très mauvais
836
+ if (totalWeight === 0) return Infinity; // Prevent zero-division
848
837
  const normalizedWeights = weights.map((w) => w / totalWeight);
849
838
 
850
839
  let portfolioReturn = normalizedWeights.reduce(
@@ -854,8 +843,7 @@ Optimization.Operators.createPortfolioAllocator = ({
854
843
  let portfolioVolatility;
855
844
 
856
845
  if (covarianceMatrix) {
857
- // Calcul de la volatilité avec la matrice de covariance (plus précis)
858
- // Volatilité^2 = w' * C * w
846
+ // Accurate covariance volatility: Volatility^2 = w' * C * w
859
847
  let variance = 0;
860
848
  for (let i = 0; i < assets.length; i++) {
861
849
  for (let j = 0; j < assets.length; j++) {
@@ -867,7 +855,7 @@ Optimization.Operators.createPortfolioAllocator = ({
867
855
  }
868
856
  portfolioVolatility = Math.sqrt(variance);
869
857
  } else {
870
- // Calcul simplifié (moins précis) : moyenne pondérée des volatilités individuelles.
858
+ // Simplified weighted average volatility
871
859
  portfolioVolatility = normalizedWeights.reduce(
872
860
  (sum, w, i) => sum + w * assets[i].volatility,
873
861
  0,
@@ -876,39 +864,38 @@ Optimization.Operators.createPortfolioAllocator = ({
876
864
 
877
865
  for (let i = 0; i < assets.length; i++) {}
878
866
 
879
- // Forte pénalité si la contrainte de risque n'est pas respectée.
867
+ // High penalty if risk ceiling is violated
880
868
  if (portfolioVolatility > maxVolatility) {
881
- return 1000 + (portfolioVolatility - maxVolatility) * 1000; // Pénalité proportionnelle à la violation.
869
+ return 1000 + (portfolioVolatility - maxVolatility) * 1000; // Proportional penalty
882
870
  }
883
871
 
884
- // On veut maximiser le rendement, donc on minimise son opposé.
872
+ // Maximize return <=> minimize negative return
885
873
  return -portfolioReturn;
886
874
  };
887
875
  };
888
876
 
889
877
  /**
890
- * Crée un solveur complet pour le problème du voyageur de commerce (TSP) en utilisant le Recuit Simulé.
891
- * Cette fonction factorise la création de l'évaluateur de chemin et de la fonction de voisinage.
892
- * @param {Array<{x: number, y: number}>} cities - Un tableau d'objets représentant les coordonnées des villes.
893
- * @param {object} [options] - Options pour l'algorithme de recuit simulé.
894
- * @returns {{solution: Array<number>, energy: number}} Le chemin optimal (indices des villes) et sa distance.
878
+ * Solves the Traveling Salesperson Problem (TSP) using Simulated Annealing.
879
+ * @param {Array<{x: number, y: number}>} cities - City coordinates array.
880
+ * @param {object} [options] - Simulated Annealing parameters.
881
+ * @returns {{solution: Array<number>, energy: number}} Optimal tour (city indices) and total distance.
895
882
  */
896
883
  Optimization.Operators.solveTSP = (cities, options = {}) => {
897
- // Fonction interne pour calculer la distance entre deux villes.
884
+ // Euclidean distance between two cities
898
885
  const distance = (city1, city2) =>
899
886
  Math.sqrt(Math.pow(city1.x - city2.x, 2) + Math.pow(city1.y - city2.y, 2));
900
887
 
901
- // Évaluateur : calcule la longueur totale d'un chemin donné.
888
+ // Total route tour distance evaluator
902
889
  const pathEvaluator = (path) => {
903
890
  let totalDistance = 0;
904
891
  for (let i = 0; i < path.length - 1; i++) {
905
892
  totalDistance += distance(cities[path[i]], cities[path[i + 1]]);
906
893
  }
907
- totalDistance += distance(cities[path[path.length - 1]], cities[path[0]]); // Retour au départ
894
+ totalDistance += distance(cities[path[path.length - 1]], cities[path[0]]); // Return to origin
908
895
  return totalDistance;
909
896
  };
910
897
 
911
- // Voisinage : génère un chemin voisin en inversant une sous-séquence (heuristique 2-opt).
898
+ // Neighborhood: inverts a random sub-sequence (2-opt heuristic)
912
899
  const pathNeighbor = (path) => {
913
900
  const newPath = [...path];
914
901
  if (newPath.length <= 1) return newPath;
@@ -924,12 +911,12 @@ Optimization.Operators.solveTSP = (cities, options = {}) => {
924
911
  return newPath;
925
912
  };
926
913
 
927
- // Solution initiale : un chemin aléatoire.
914
+ // Initial solution: randomized route
928
915
  const initialPath = Array.from({ length: cities.length }, (_, i) => i).sort(
929
916
  () => random() - 0.5,
930
917
  );
931
918
 
932
- // Paramètres par défaut pour le TSP, pouvant être surchargés par `options`.
919
+ // Default hyperparameters for TSP
933
920
  const saOptions = {
934
921
  initialTemperature: 10000,
935
922
  coolingRate: 0.999,
@@ -948,35 +935,35 @@ Optimization.Operators.solveTSP = (cities, options = {}) => {
948
935
  };
949
936
 
950
937
  /**
951
- * Crée un solveur complet pour le problème d'optimisation de portefeuille en utilisant un Algorithme Génétique.
952
- * @param {Array<{name: string, expectedReturn: number, volatility: number}>} assets - Les actifs disponibles.
953
- * @param {number} maxVolatility - La contrainte de volatilité maximale du portefeuille.
954
- * @param {object} [options] - Options pour l'algorithme génétique.
955
- * @returns {{solution: Array<number>, fitness: number}} L'allocation de poids optimale et le score de fitness associé.
938
+ * Solves portfolio allocation using a Genetic Algorithm.
939
+ * @param {Array<{name: string, expectedReturn: number, volatility: number}>} assets - Available assets.
940
+ * @param {number} maxVolatility - Maximum allowed volatility constraint.
941
+ * @param {object} [options] - Genetic algorithm options.
942
+ * @returns {{solution: Array<number>, fitness: number}} Optimal weights and associated fitness.
956
943
  */
957
944
  Optimization.Operators.solvePortfolio = (
958
945
  assets,
959
946
  maxVolatility,
960
947
  options = {},
961
948
  ) => {
962
- // La fonction de fitness est créée par notre opérateur existant.
949
+ // Fitness function created from the allocator operator
963
950
  const fitnessFunction = Optimization.Operators.createPortfolioAllocator({
964
951
  assets,
965
952
  maxVolatility,
966
953
  covarianceMatrix: options.covarianceMatrix,
967
954
  });
968
955
 
969
- // Fonctions spécifiques au problème pour l'AG, maintenant encapsulées.
956
+ // Problem-specific genetic operators
970
957
  const createIndividual = () =>
971
958
  Array.from({ length: assets.length }, () => random());
972
959
 
973
- const crossover = (p1, p2) => p1.map((w1, i) => (w1 + p2[i]) / 2); // Moyenne des poids
960
+ const crossover = (p1, p2) => p1.map((w1, i) => (w1 + p2[i]) / 2); // Arithmetic average
974
961
 
975
962
  const mutate = (p) => {
976
963
  const newP = [...p];
977
964
  const i = crypto.randomInt(0, newP.length);
978
- newP[i] += (random() - 0.5) * 0.2; // Mutation douce
979
- newP[i] = Math.max(0, newP[i]); // Les poids ne peuvent être négatifs
965
+ newP[i] += (random() - 0.5) * 0.2; // Gentle mutation
966
+ newP[i] = Math.max(0, newP[i]); // Weights cannot be negative
980
967
  return newP;
981
968
  };
982
969
 
@@ -996,57 +983,56 @@ Optimization.Operators.solvePortfolio = (
996
983
  };
997
984
 
998
985
  /**
999
- * Crée un évaluateur 2D pour trouver la commission de base et le facteur de bonus qualité qui maximisent les revenus de la plateforme.
1000
- * @param {object} config - L'objet de configuration.
1001
- * @param {number} config.totalAdvertiserCredits - Le total des crédits disponibles chez les annonceurs.
1002
- * @param {Array<{qualityScore: number}>} config.websites - Un tableau d'objets représentant les sites monétisés, chacun avec un score de qualité.
1003
- * @returns {function(Array<number>): number} Un évaluateur qui prend une solution `[baseCommission, bonusFactor]` et retourne le revenu NÉGATIF (car les solveurs minimisent).
986
+ * Creates a 2D evaluator to determine base commission and quality bonus factor to maximize platform revenue.
987
+ * @param {object} config - Configuration object.
988
+ * @param {number} config.totalAdvertiserCredits - Total advertiser credits.
989
+ * @param {Array<{qualityScore: number}>} config.websites - Array of websites with quality scores.
990
+ * @returns {function(Array<number>): number} Evaluator for `[baseCommission, bonusFactor]` returning NEGATIVE revenue.
1004
991
  */
1005
992
  Optimization.Operators.createAdvancedPlatformRevenueEvaluator = ({
1006
993
  totalAdvertiserCredits,
1007
994
  websites,
1008
995
  }) => {
1009
- // Modèle de la demande (Annonceurs)
1010
- // La demande est sensible à la qualité globale de l'inventaire publicitaire.
996
+ // Advertiser demand model (increases with inventory quality)
1011
997
  const advertiserDemandModel = (averageSiteQuality) => {
1012
- // La demande de base est toujours liée aux crédits disponibles.
998
+ // Baseline demand tied to available credits
1013
999
  const baseDemand = (totalAdvertiserCredits || 100) * 10;
1014
- // La demande augmente avec la qualité moyenne des sites.
1000
+ // Demand scales with average inventory quality
1015
1001
  return baseDemand * (1 + averageSiteQuality);
1016
1002
  };
1017
1003
 
1018
- // Modèle de l'offre (Webmasters)
1019
- // L'offre de chaque site dépend de sa rémunération individuelle.
1004
+ // Publisher / webmaster supply model
1005
+ // Each site's click supply scales with its effective payout rate
1020
1006
  const webmasterSupplyModel = (baseCommission, bonusFactor) => {
1021
1007
  let totalOfferedClicks = 0;
1022
- const baseSupplyPerSite = 500; // Clics potentiels par site
1008
+ const baseSupplyPerSite = 500; // Baseline potential clicks per site
1023
1009
 
1024
1010
  for (const site of websites) {
1025
- // La commission effective est réduite pour les sites de haute qualité.
1011
+ // Effective commission is discounted for higher quality sites
1026
1012
  const effectiveCommission = Math.max(
1027
1013
  0,
1028
1014
  baseCommission - site.qualityScore * bonusFactor,
1029
1015
  );
1030
1016
  const webmasterPayoutRate = 1 - effectiveCommission;
1031
1017
 
1032
- // L'offre d'un site est proportionnelle à son taux de rémunération.
1018
+ // Supply is proportional to payout rate
1033
1019
  totalOfferedClicks += baseSupplyPerSite * webmasterPayoutRate;
1034
1020
  }
1035
1021
  return totalOfferedClicks;
1036
1022
  };
1037
1023
 
1038
- // L'évaluateur pour l'algorithme d'optimisation (Recuit Simulé, etc.)
1024
+ // Objective function for optimization algorithms
1039
1025
  return function revenueEvaluator(solution) {
1040
1026
  const [baseCommission, bonusFactor] = solution;
1041
1027
 
1042
- // Contraintes : on pénalise fortement les solutions hors des clous.
1028
+ // Constraints: heavily penalize out-of-bound solutions
1043
1029
  if (
1044
1030
  baseCommission < 0.01 ||
1045
1031
  baseCommission > 0.8 ||
1046
1032
  bonusFactor < 0 ||
1047
1033
  bonusFactor > baseCommission
1048
1034
  ) {
1049
- return Infinity; // Score très mauvais
1035
+ return Infinity;
1050
1036
  }
1051
1037
 
1052
1038
  const averageQuality =
@@ -1064,19 +1050,19 @@ Optimization.Operators.createAdvancedPlatformRevenueEvaluator = ({
1064
1050
  baseCommission - averageQuality * bonusFactor,
1065
1051
  );
1066
1052
 
1067
- // On veut MAXIMISER le revenu, donc on MINIMISE son opposé.
1053
+ // Maximize revenue <=> minimize negative revenue
1068
1054
  return -(clicks * averageCommission);
1069
1055
  };
1070
1056
  };
1071
1057
 
1072
1058
  /**
1073
- * Crée un solveur pour le problème de placement d'infrastructures (Facility Location Problem).
1074
- * @param {Array<{x: number, y: number}>} customers - Coordonnées des clients.
1075
- * @param {number} numFacilities - Le nombre d'infrastructures à placer.
1076
- * @param {{minX: number, maxX: number, minY: number, maxY: number}} bounds - Les limites de la carte où placer les infrastructures.
1077
- * @param {number} [options.fixedCostPerFacility=0] - Coût fixe pour chaque infrastructure installée.
1078
- * @param {object} [options] - Options pour le recuit simulé.
1079
- * @returns {{solution: Array<{x: number, y: number}>, energy: number}} Les coordonnées optimales des infrastructures et le coût total.
1059
+ * Solves the Facility Location Problem using Simulated Annealing.
1060
+ * @param {Array<{x: number, y: number}>} customers - Customer coordinate pairs.
1061
+ * @param {number} numFacilities - Number of facilities to place.
1062
+ * @param {{minX: number, maxX: number, minY: number, maxY: number}} bounds - Boundary box for placing facilities.
1063
+ * @param {number} [options.fixedCostPerFacility=0] - Fixed installation cost per facility.
1064
+ * @param {object} [options] - Simulated Annealing options.
1065
+ * @returns {{solution: Array<{x: number, y: number}>, energy: number}} Optimal facility locations and total cost.
1080
1066
  */
1081
1067
  Optimization.Operators.solveFacilityLocation = (
1082
1068
  customers,
@@ -1086,9 +1072,9 @@ Optimization.Operators.solveFacilityLocation = (
1086
1072
  ) => {
1087
1073
  const fixedCostPerFacility = options.fixedCostPerFacility || 0;
1088
1074
  const distanceSq = (p1, p2) =>
1089
- Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2); // On utilise la distance au carré pour l'efficacité
1075
+ Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2); // Squared Euclidean distance for efficiency
1090
1076
 
1091
- // Évaluateur : calcule la somme des distances de chaque client à son infrastructure la plus proche.
1077
+ // Evaluator: computes sum of shortest distances from each customer to nearest facility
1092
1078
  const facilityEvaluator = (facilities) => {
1093
1079
  let totalConnectionCost = 0;
1094
1080
  for (const customer of customers) {
@@ -1099,13 +1085,13 @@ Optimization.Operators.solveFacilityLocation = (
1099
1085
  minDistanceToCustomer = d;
1100
1086
  }
1101
1087
  }
1102
- totalConnectionCost += Math.sqrt(minDistanceToCustomer); // On utilise la vraie distance pour le coût
1088
+ totalConnectionCost += Math.sqrt(minDistanceToCustomer); // True Euclidean distance for cost
1103
1089
  }
1104
- // Le coût total est la somme des coûts de connexion + le coût fixe des infrastructures.
1090
+ // Total cost = connection cost + fixed facility maintenance costs
1105
1091
  return totalConnectionCost + facilities.length * fixedCostPerFacility;
1106
1092
  };
1107
1093
 
1108
- // Voisinage : déplace légèrement une infrastructure au hasard.
1094
+ // Neighborhood: randomly perturb one facility position
1109
1095
  const facilityNeighbor = (facilities) => {
1110
1096
  const newFacilities = facilities.map((f) => ({ ...f }));
1111
1097
  const i = crypto.randomInt(0, numFacilities);
@@ -1124,7 +1110,7 @@ Optimization.Operators.solveFacilityLocation = (
1124
1110
  return newFacilities;
1125
1111
  };
1126
1112
 
1127
- // Solution initiale : place les infrastructures au hasard sur la carte.
1113
+ // Initial solution: distribute facilities randomly across bounds
1128
1114
  const initialFacilities = Array.from({ length: numFacilities }, () => ({
1129
1115
  x: bounds.minX + random() * (bounds.maxX - bounds.minX),
1130
1116
  y: bounds.minY + random() * (bounds.maxY - bounds.minY),
@@ -1149,65 +1135,59 @@ Optimization.Operators.solveFacilityLocation = (
1149
1135
  return result;
1150
1136
  };
1151
1137
 
1152
- // --- SOLUTION : Définir un coût de base pour un clic ---
1153
- // 1 PRIM'S = 1 clic
1138
+ // Baseline click cost: 1 credit = 1 click
1154
1139
  const BASE_CLICK_COST = 1;
1155
1140
  /**
1156
- * Crée un évaluateur multi-objectifs pour déterminer le Coût Par Clic (CPC) optimal.
1157
- * @param {object} config - L'objet de configuration.
1158
- * @param {object} config.advertiser - L'annonceur qui paie le clic.
1159
- * @param {object} config.ad - L'annonce qui a été cliquée.
1160
- * @param {Array<object>} config.competingAds - Les autres annonces ciblant les mêmes mots-clés.
1161
- * @param {object} config.website - Le site sur lequel le clic a eu lieu.
1162
- * @param {object} config.platformParams - Les paramètres de la plateforme (taux de commission, etc.).
1163
- * @param {number} config.estimatedImpressions - Le nombre d'impressions quotidiennes estimées pour ce contexte.
1141
+ * Creates a multi-objective evaluator to determine optimal Cost Per Click (CPC).
1142
+ * @param {object} context - Configuration context.
1143
+ * @param {object} context.advertiser - Advertiser paying for the click.
1144
+ * @param {object} context.ad - Ad details.
1145
+ * @param {Array<object>} context.competingAds - Competing ads targeting the same keywords.
1146
+ * @param {object} context.website - Publisher website hosting the ad.
1147
+ * @param {object} context.platformParams - Platform commission parameters.
1148
+ * @param {number} context.estimatedImpressions - Estimated daily impressions.
1164
1149
  */
1165
1150
  Optimization.Operators.createOptimalCPCEvaluator = (context) => {
1166
1151
  const { optimalBaseCommission, optimalBonusFactor } = context.platformParams;
1167
1152
  const websiteQualityScore = (context.website?.relevanceScore || 50) / 100;
1168
1153
 
1169
- // Le taux de commission effectif pour ce site
1154
+ // Effective commission rate for this publisher
1170
1155
  const effectiveCommissionRate = Math.max(
1171
1156
  0,
1172
1157
  optimalBaseCommission - websiteQualityScore * optimalBonusFactor,
1173
1158
  );
1174
1159
 
1175
- // Modèle de la demande : combien de clics l'annonceur peut-il s'offrir ?
1160
+ // Demand model: how many clicks can the advertiser afford?
1176
1161
  const advertiserDemand = (cpc) => {
1177
1162
  if (cpc <= 0) return Infinity;
1178
1163
  return (context.advertiser.credits || 0) / cpc;
1179
1164
  };
1180
1165
 
1181
- // --- SOLUTION : Utiliser l'offre réelle et la concurrence ---
1182
- // L'offre est maintenant le nombre d'impressions estimées, un chiffre concret.
1183
- const supply = context.estimatedImpressions || 1; // Fallback à 1 pour éviter la division par zéro.
1166
+ // Available inventory supply
1167
+ const supply = context.estimatedImpressions || 1; // Fallback to 1 to prevent zero-division
1184
1168
 
1185
- // Le facteur de concurrence augmente le prix s'il y a plus de monde sur le même créneau.
1186
- // Formule simple : 1 + (0.1 * nombre de concurrents), avec un plafond.
1169
+ // Competition factor scales price based on competition density (capped at 2.5)
1187
1170
  const competitionFactor = Math.min(
1188
1171
  2.5,
1189
1172
  1 + context.competingAds.length * 0.1,
1190
1173
  );
1191
1174
 
1192
1175
  return function cpcFitness(cpcMultiplier) {
1193
- // Le CPC final est le coût de base, ajusté par le multiplicateur de l'algo et la concurrence.
1176
+ // Final CPC is baseline cost adjusted by optimizer multiplier and competition factor
1194
1177
  const adjustedCPC = BASE_CLICK_COST * cpcMultiplier * competitionFactor;
1195
- if (adjustedCPC < 0.1) return [Infinity, Infinity, Infinity]; // CPC minimum
1178
+ if (adjustedCPC < 0.1) return [Infinity, Infinity, Infinity]; // Minimum CPC constraint
1196
1179
 
1197
- // La demande de l'annonceur est calculée avec le CPC ajusté.
1198
1180
  const demand = advertiserDemand(adjustedCPC);
1199
-
1200
- // Le nombre de clics est le minimum de l'offre et de la demande.
1201
1181
  const estimatedClicks = Math.min(demand, supply);
1202
1182
 
1203
- // Objectif 1 : Maximiser le revenu de la plateforme (donc minimiser son opposé)
1183
+ // Objective 1: Maximize platform revenue (minimize negative revenue)
1204
1184
  const platformRevenue =
1205
1185
  estimatedClicks * adjustedCPC * effectiveCommissionRate;
1206
1186
 
1207
- // Objectif 2 : Maximiser la valeur pour l'annonceur (nombre de clics, donc minimiser son opposé)
1187
+ // Objective 2: Maximize advertiser value (clicks delivered, minimize negative)
1208
1188
  const advertiserValue = estimatedClicks;
1209
1189
 
1210
- // Objectif 3 : Minimiser le déséquilibre du marché (offre vs demande)
1190
+ // Objective 3: Minimize market imbalance (supply vs demand)
1211
1191
  const marketImbalance = Math.abs(demand - supply);
1212
1192
 
1213
1193
  return [-platformRevenue, -advertiserValue, marketImbalance];
@@ -1215,47 +1195,44 @@ Optimization.Operators.createOptimalCPCEvaluator = (context) => {
1215
1195
  };
1216
1196
 
1217
1197
  /**
1218
- * Applique le facteur de concurrence au CPC de base.
1198
+ * Applies competition factor to baseline CPC.
1219
1199
  * @private
1220
- * @param {number} baseCpc - Le CPC issu de l'algorithme génétique.
1221
- * @param {Array<object>} competingAds - Les annonces concurrentes.
1222
- * @returns {number} Le CPC final ajusté.
1200
+ * @param {number} baseCpc - Baseline CPC from genetic algorithm.
1201
+ * @param {Array<object>} competingAds - Competing ads.
1202
+ * @returns {number} Final adjusted CPC.
1223
1203
  */
1224
1204
  function applyCompetitionFactor(baseCpc, competingAds) {
1225
- // Formule simple : 1 + (0.1 * nombre de concurrents), avec un plafond pour éviter l'explosion des prix.
1226
1205
  const competitionFactor = Math.min(
1227
1206
  2.5,
1228
1207
  1 + (competingAds || []).length * 0.1,
1229
1208
  );
1230
1209
  const finalCpc = baseCpc * competitionFactor;
1231
- // On s'assure de ne jamais descendre sous un seuil minimal.
1232
1210
  return Math.max(0.1, finalCpc);
1233
1211
  }
1234
1212
  /**
1235
- * Résout le problème du CPC optimal en utilisant un algorithme génétique multi-objectifs.
1236
- * @param {object} context - Le contexte nécessaire pour l'évaluation (advertiser, ad, etc.).
1237
- * @param {object} [options] - Options pour l'algorithme génétique.
1238
- * @returns {Array<{solution: number, objectives: number[]}>} Le front de Pareto des solutions CPC.
1213
+ * Solves optimal CPC configuration using a multi-objective genetic algorithm.
1214
+ * @param {object} context - Context required for evaluation (advertiser, ad, etc.).
1215
+ * @param {object} [options] - Genetic algorithm options.
1216
+ * @returns {Array<{solution: number, objectives: number[]}>} Pareto front of optimal CPC solutions.
1239
1217
  */
1240
1218
  Optimization.Operators.solveOptimalCPC = (context, options = {}) => {
1241
1219
  const fitnessFunction =
1242
1220
  Optimization.Operators.createOptimalCPCEvaluator(context);
1243
1221
 
1244
- // Un "individu" est simplement une valeur de CPC.
1222
+ // An individual is a CPC multiplier scalar
1245
1223
  const createIndividual = () => {
1246
- // Le CPC peut varier, par exemple, entre 0.5 et 5 PRIM'S.
1247
1224
  return 0.5 + random() * 4.5;
1248
1225
  };
1249
1226
 
1250
- // Croisement : moyenne des CPC des parents.
1227
+ // Crossover: average of parent multipliers
1251
1228
  const crossover = (cpc1, cpc2) => {
1252
1229
  return (cpc1 + cpc2) / 2;
1253
1230
  };
1254
1231
 
1255
- // Mutation : légère variation aléatoire du CPC.
1232
+ // Mutation: slight random variation
1256
1233
  const mutate = (cpc) => {
1257
1234
  const newCpc = cpc + (random() - 0.5) * 0.5;
1258
- return Math.max(0.1, newCpc); // Assurer un CPC minimum.
1235
+ return Math.max(0.1, newCpc);
1259
1236
  };
1260
1237
 
1261
1238
  const gaOptions = {
@@ -1264,7 +1241,6 @@ Optimization.Operators.solveOptimalCPC = (context, options = {}) => {
1264
1241
  ...options,
1265
1242
  };
1266
1243
 
1267
- // On utilise l'algorithme génétique multi-objectifs pour obtenir le front de Pareto.
1268
1244
  const paretoFront = Optimization.geneticAlgorithmMultiObjective(
1269
1245
  createIndividual,
1270
1246
  fitnessFunction,
@@ -1273,60 +1249,53 @@ Optimization.Operators.solveOptimalCPC = (context, options = {}) => {
1273
1249
  gaOptions,
1274
1250
  );
1275
1251
 
1276
- // --- SOLUTION : Appliquer le facteur de concurrence sur les solutions finales ---
1252
+ // Apply competition factor to final Pareto solutions
1277
1253
  return paretoFront.map((result) => {
1278
1254
  const cpcMultiplier = result.solution;
1279
- // Le CPC final est calculé ici, en dehors de la fonction de fitness.
1280
1255
  const finalCpc = applyCompetitionFactor(
1281
1256
  BASE_CLICK_COST * cpcMultiplier,
1282
1257
  context.competingAds,
1283
1258
  );
1284
1259
  return {
1285
1260
  ...result,
1286
- solution: Math.max(0.1, finalCpc), // On s'assure de ne jamais descendre sous le plancher absolu.
1261
+ solution: Math.max(0.1, finalCpc),
1287
1262
  };
1288
1263
  });
1289
1264
  };
1290
1265
 
1291
1266
  /**
1292
- * Crée un évaluateur multi-objectifs pour trouver le TTL (Time-To-Live) optimal pour un ticket de sécurité.
1293
- * @param {object} context - L'objet de configuration.
1294
- * @param {number} context.suspicionScore - Le score de suspicion de l'utilisateur (0-100).
1295
- * @returns {function(number): number[]} Une fonction de fitness qui prend un TTL (en ms) et retourne les scores des objectifs [risque, friction].
1267
+ * Creates a multi-objective evaluator to find optimal TTL (Time-To-Live) for a security ticket.
1268
+ * @param {object} context - Configuration context.
1269
+ * @param {number} context.suspicionScore - User suspicion score (0-100).
1270
+ * @returns {function(number): number[]} Fitness function returning [risk, friction] objective scores.
1296
1271
  */
1297
1272
  Optimization.Operators.createOptimalTtlEvaluator = ({ suspicionScore }) => {
1298
- // On normalise le score pour qu'il soit plus impactant dans le calcul du risque.
1273
+ // Normalize score to ensure strong weight in risk calculation
1299
1274
  const normalizedScore = Math.max(1, suspicionScore);
1300
1275
 
1301
1276
  return function ttlFitness(ttl) {
1302
- // Contraintes : un TTL doit être dans une plage raisonnable (ex: 5min à 24h)
1277
+ // Constraints: TTL must be within realistic bounds (5m to 24h)
1303
1278
  if (ttl < 300000 || ttl > 86400000) return [Infinity, Infinity];
1304
1279
 
1305
- // Objectif 1 : Minimiser le Risque.
1306
- // Le risque est le produit du score et de la durée de la session.
1307
- // Pour un score élevé, l'algo doit choisir un TTL faible pour minimiser ce produit.
1280
+ // Objective 1: Minimize Security Risk (product of suspicion score and session duration)
1308
1281
  const risk = normalizedScore * ttl;
1309
1282
 
1310
- // Objectif 2 : Minimiser la Friction UX.
1311
- // La friction est l'inverse du TTL. On la pénalise d'autant plus que le score est FAIBLE.
1312
- // (101 - score) assure que pour un score de 1, la pénalité d'un TTL court est maximale.
1313
- // Pour un score de 100, cette pénalité est quasi nulle.
1283
+ // Objective 2: Minimize UX Friction (inverse of TTL, penalized when suspicion is low)
1314
1284
  const friction = (1 / ttl) * (101 - normalizedScore);
1315
1285
 
1316
- // On retourne 2 objectifs avec des facteurs de mise à l'échelle pour les équilibrer.
1286
+ // Return scaled objectives to balance dimensions
1317
1287
  return [risk / 1e7, friction * 1e9];
1318
1288
  };
1319
1289
  };
1320
1290
 
1321
1291
  /**
1322
- * Calcule la déviation d'une série de chiffres par rapport à la loi de Benford.
1323
- * Un score élevé indique une distribution non naturelle, potentiellement frauduleuse.
1324
- * @param {string} numberString - Une chaîne de chiffres (ex: "123456789").
1325
- * @returns {number} Un score de déviation (0 = parfait, > 0.15 = suspect).
1292
+ * Calculates Benford's Law deviation for a series of numbers.
1293
+ * Elevated scores indicate unnatural/synthetic data distributions.
1294
+ * @param {Array<number|string>} numbers - Array of numeric samples.
1295
+ * @returns {number} Deviation score (0 = perfect match, > 0.15 = suspicious).
1326
1296
  */
1327
1297
  Optimization.Operators.benfordTest = (numbers) => {
1328
1298
  if (!Array.isArray(numbers)) {
1329
- // Si l'entrée n'est pas un tableau, on ne peut pas l'analyser.
1330
1299
  return 0;
1331
1300
  }
1332
1301
  const counts = Array(10).fill(0);
@@ -1354,12 +1323,12 @@ Optimization.Operators.benfordTest = (numbers) => {
1354
1323
  }
1355
1324
 
1356
1325
  if (validCount < 10) {
1357
- return 0; // Pas assez de données pour un test fiable
1326
+ return 0; // Insufficient data points for reliable statistical testing
1358
1327
  }
1359
1328
 
1360
- // Distribution attendue selon la loi de Benford pour le premier chiffre
1329
+ // Expected Benford's Law distribution for leading digits 1 through 9
1361
1330
  const benfordDistribution = [
1362
- 0, // Index 0 non utilisé
1331
+ 0, // Index 0 unused
1363
1332
  30.1, 17.6, 12.5, 9.7, 7.9, 6.7, 5.8, 5.1, 4.6
1364
1333
  ];
1365
1334
 
@@ -1370,8 +1339,7 @@ Optimization.Operators.benfordTest = (numbers) => {
1370
1339
  totalDeviation += Math.pow(observedFrequency - expectedFrequency, 2);
1371
1340
  }
1372
1341
 
1373
- // Normalise la déviation pour obtenir un score plus interprétable.
1374
- // Cette normalisation est empirique.
1342
+ // Normalize deviation into an interpretable metric scale
1375
1343
  return Math.sqrt(totalDeviation) / 50;
1376
1344
  };
1377
1345
 
@@ -1379,17 +1347,17 @@ Optimization.Operators.benfordTest = (numbers) => {
1379
1347
 
1380
1348
 
1381
1349
  /**
1382
- * Crée un évaluateur multi-objectifs pour trouver les seuils de détection de fraude optimaux.
1383
- * @param {object} config - L'objet de configuration.
1384
- * @param {Array<object>} config.legitimateClicks - Un échantillon de clics considérés comme légitimes.
1385
- * @param {Array<object>} config.fraudulentClicks - Un échantillon de clics identifiés comme frauduleux (ex: honeypots).
1386
- * @returns {function(Array<number>): number[]} Une fonction de fitness qui prend une solution `[minTimeToClick, maxClickVariance, minMouseEntropy, minScrollEvents]` et retourne les scores des objectifs.
1350
+ * Creates a multi-objective evaluator to identify optimal fraud detection thresholds.
1351
+ * @param {object} config - Configuration object.
1352
+ * @param {Array<object>} config.legitimateClicks - Sample of legitimate click events.
1353
+ * @param {Array<object>} config.fraudulentClicks - Sample of identified fraudulent clicks (e.g. honeypots).
1354
+ * @returns {function(Array<number>): number[]} Fitness function returning [1 - TPR, FPR].
1387
1355
  */
1388
1356
  Optimization.Operators.createFraudThresholdEvaluator = ({
1389
1357
  legitimateClicks,
1390
1358
  fraudulentClicks,
1391
1359
  }) => {
1392
- // Fonction d'aide pour calculer la variance des positions de clic pour une empreinte
1360
+ // Helper to compute click coordinate variance for a given fingerprint
1393
1361
  const calculateClickVariance = (clicks) => {
1394
1362
  if (!clicks || clicks.length < 2) return 0;
1395
1363
  const meanX = clicks.reduce((sum, c) => sum + c.clickX, 0) / clicks.length;
@@ -1403,7 +1371,7 @@ Optimization.Operators.createFraudThresholdEvaluator = ({
1403
1371
  return variance;
1404
1372
  };
1405
1373
 
1406
- // Pré-calculer la variance pour chaque empreinte dans les données
1374
+ // Pre-group click events by fingerprint
1407
1375
  const getClicksByFingerprint = (clickData) => {
1408
1376
  const grouped = {};
1409
1377
  for (const click of clickData) {
@@ -1420,7 +1388,7 @@ Optimization.Operators.createFraudThresholdEvaluator = ({
1420
1388
  const [minTimeToClick, maxClickVariance, minMouseEntropy, minScrollEvents] =
1421
1389
  solution;
1422
1390
 
1423
- // Contraintes pour garder des seuils logiques
1391
+ // Logical threshold boundary constraints
1424
1392
  if (
1425
1393
  minTimeToClick < 100 ||
1426
1394
  minTimeToClick > 5000 ||
@@ -1430,18 +1398,17 @@ Optimization.Operators.createFraudThresholdEvaluator = ({
1430
1398
  minMouseEntropy > 1 ||
1431
1399
  minScrollEvents < 0
1432
1400
  ) {
1433
- return [Infinity, Infinity]; // Mauvais score si hors limites
1401
+ return [Infinity, Infinity];
1434
1402
  }
1435
1403
 
1436
- let truePositives = 0; // Bots correctement identifiés
1437
- let falsePositives = 0; // Humains incorrectement bloqués
1404
+ let truePositives = 0; // Bots accurately detected
1405
+ let falsePositives = 0; // Humans falsely flagged
1438
1406
 
1439
- // Évaluer les clics frauduleux
1407
+ // Evaluate fraudulent clicks
1440
1408
  for (const fingerprint in fraudulentGroups) {
1441
1409
  const clicks = fraudulentGroups[fingerprint];
1442
1410
  if (!clicks) continue;
1443
1411
  const variance = calculateClickVariance(clicks);
1444
- // Un groupe de clics est frauduleux si l'une des conditions est remplie
1445
1412
  const isTooFast = clicks.some((c) => c.timeToClick < minTimeToClick);
1446
1413
  const isTooUniform = variance < maxClickVariance;
1447
1414
  const hasLowEntropy = clicks.some(
@@ -1456,7 +1423,7 @@ Optimization.Operators.createFraudThresholdEvaluator = ({
1456
1423
  }
1457
1424
  }
1458
1425
 
1459
- // Évaluer les clics légitimes
1426
+ // Evaluate legitimate clicks
1460
1427
  for (const fingerprint in legitimateGroups) {
1461
1428
  const clicks = legitimateGroups[fingerprint];
1462
1429
  if (!clicks) continue;
@@ -1477,10 +1444,10 @@ Optimization.Operators.createFraudThresholdEvaluator = ({
1477
1444
  const totalFraudulent = Object.keys(fraudulentGroups).length || 1;
1478
1445
  const totalLegitimate = Object.keys(legitimateGroups).length || 1;
1479
1446
 
1480
- // Objectif 1 : Maximiser la détection de fraude (donc minimiser 1 - taux de détection)
1447
+ // Objective 1: Maximize fraud detection rate (minimize 1 - TPR)
1481
1448
  const objective1 = 1 - truePositives / totalFraudulent;
1482
1449
 
1483
- // Objectif 2 : Minimiser le taux de faux positifs
1450
+ // Objective 2: Minimize false positive rate (minimize FPR)
1484
1451
  const objective2 = falsePositives / totalLegitimate;
1485
1452
 
1486
1453
  return [objective1, objective2];
@@ -1488,27 +1455,27 @@ Optimization.Operators.createFraudThresholdEvaluator = ({
1488
1455
  };
1489
1456
 
1490
1457
  /**
1491
- * Résout le problème de la détection de fraude en trouvant un front de Pareto de seuils optimaux.
1492
- * @param {object} context - Le contexte contenant les données de clics.
1493
- * @param {Array<object>} context.legitimateClicks - Échantillon de clics légitimes.
1494
- * @param {Array<object>} context.fraudulentClicks - Échantillon de clics frauduleux.
1495
- * @param {object} [options] - Options pour l'algorithme génétique.
1496
- * @returns {Array<{solution: Array<number>, objectives: number[]}>} Le front de Pareto des solutions [minTimeToClick, maxClickVariance, minMouseEntropy, minScrollEvents].
1458
+ * Solves fraud detection optimization by finding the Pareto front of threshold values.
1459
+ * @param {object} context - Context containing click datasets.
1460
+ * @param {Array<object>} context.legitimateClicks - Sample of legitimate clicks.
1461
+ * @param {Array<object>} context.fraudulentClicks - Sample of fraudulent clicks.
1462
+ * @param {object} [options] - Genetic algorithm options.
1463
+ * @returns {Array<{solution: Array<number>, objectives: number[]}>} Pareto front of [minTimeToClick, maxClickVariance, minMouseEntropy, minScrollEvents].
1497
1464
  */
1498
1465
  Optimization.Operators.solveFraudDetection = (context, options = {}) => {
1499
1466
  const fitnessFunction =
1500
1467
  Optimization.Operators.createFraudThresholdEvaluator(context);
1501
1468
 
1502
- // Un "individu" est un tableau de 4 seuils : [minTimeToClick, maxClickVariance, minMouseEntropy, minScrollEvents]
1469
+ // An individual is a 4-threshold array: [minTimeToClick, maxClickVariance, minMouseEntropy, minScrollEvents]
1503
1470
  const createIndividual = () => {
1504
- const minTimeToClick = 100 + random() * 4900; // entre 100ms et 5s
1505
- const maxClickVariance = 1 + random() * 9999; // entre 1 et 10000
1506
- const minMouseEntropy = random() * 0.5; // entre 0 et 0.5
1507
- const minScrollEvents = crypto.randomInt(0, 10); // entre 0 et 10
1471
+ const minTimeToClick = 100 + random() * 4900; // between 100ms and 5s
1472
+ const maxClickVariance = 1 + random() * 9999; // between 1 and 10000
1473
+ const minMouseEntropy = random() * 0.5; // between 0 and 0.5
1474
+ const minScrollEvents = crypto.randomInt(0, 10); // between 0 and 10
1508
1475
  return [minTimeToClick, maxClickVariance, minMouseEntropy, minScrollEvents];
1509
1476
  };
1510
1477
 
1511
- // Croisement : moyenne des seuils des parents
1478
+ // Crossover: arithmetic mean of parent thresholds
1512
1479
  const crossover = (s1, s2) => {
1513
1480
  return [
1514
1481
  (s1[0] + s2[0]) / 2,
@@ -1518,14 +1485,14 @@ Optimization.Operators.solveFraudDetection = (context, options = {}) => {
1518
1485
  ];
1519
1486
  };
1520
1487
 
1521
- // Mutation : légère variation aléatoire d'un des seuils
1488
+ // Mutation: slight random perturbation of a single threshold
1522
1489
  const mutate = (solution) => {
1523
1490
  const newSolution = [...solution];
1524
1491
  const i = crypto.randomInt(0, 4);
1525
- // Amplitudes de mutation différentes pour chaque seuil
1492
+ // Specific mutation step scales for each dimension
1526
1493
  const mutationFactors = [500, 1000, 0.1, 2];
1527
1494
  const mutationFactor = mutationFactors[i];
1528
- newSolution[i] += (secureRandom() - 0.5) * mutationFactor;
1495
+ newSolution[i] += (random() - 0.5) * mutationFactor;
1529
1496
  return newSolution;
1530
1497
  };
1531
1498
 
@@ -1545,11 +1512,11 @@ Optimization.Operators.solveFraudDetection = (context, options = {}) => {
1545
1512
  };
1546
1513
 
1547
1514
  /**
1548
- * Crée un évaluateur multi-objectifs pour l'auto-tuning complet de la configuration de sécurité.
1549
- * Optimise à la fois les seuils, les poids de suspicion et les paramètres de détection de patterns.
1550
- * @param {object} config - L'objet de configuration.
1551
- * @param {Array<object>} config.trafficData - Données de trafic collectées.
1552
- * @returns {function(object): number[]} Une fonction de fitness qui prend une configuration complète et retourne les scores [taux de faux positifs, taux de faux négatifs].
1515
+ * Creates a multi-objective evaluator for end-to-end security configuration auto-tuning.
1516
+ * Jointly optimizes action thresholds, suspicion weights, and pattern detection parameters.
1517
+ * @param {object} context - Configuration context.
1518
+ * @param {Array<object>} context.trafficData - Collected traffic logs.
1519
+ * @returns {function(object): number[]} Fitness function returning [weighted FPR, weighted FNR].
1553
1520
  */
1554
1521
  Optimization.Operators.createFullSecurityConfigEvaluator = (context) => {
1555
1522
  const trafficData = context.trafficData || [];
@@ -1559,25 +1526,25 @@ Optimization.Operators.createFullSecurityConfigEvaluator = (context) => {
1559
1526
  const THREAT_PROFILES = {
1560
1527
  account_takeover: {
1561
1528
  importance: 10.0,
1562
- ux_vs_security_ratio: 0.1, // 10% FPR / 90% FNR (Priorité sécurité maximale)
1529
+ ux_vs_security_ratio: 0.1, // 10% FPR / 90% FNR (Highest security priority)
1563
1530
  target_threshold: 'block',
1564
1531
  indicators: ['requestPatternScore', 'behaviorScore', 'timeInconsistencyScore', 'clickVarianceScore']
1565
1532
  },
1566
1533
  active_exploitation: {
1567
1534
  importance: 8.0,
1568
- ux_vs_security_ratio: 0.2, // 20% FPR / 80% FNR (Sécurité prioritaire)
1535
+ ux_vs_security_ratio: 0.2, // 20% FPR / 80% FNR (Security prioritized)
1569
1536
  target_threshold: 'block',
1570
1537
  indicators: ['honeypotScore', 'headerAnomalyScore']
1571
1538
  },
1572
1539
  mass_scraping: {
1573
1540
  importance: 3.0,
1574
- ux_vs_security_ratio: 0.8, // 80% FPR / 20% FNR (UX prioritaire)
1541
+ ux_vs_security_ratio: 0.8, // 80% FPR / 20% FNR (UX prioritized)
1575
1542
  target_threshold: 'low',
1576
- indicators: ['requestPatternScore', 'renderingAnomalyScore', 'clientHintsInconsistencyScore']
1543
+ indicators: ['requestPatternScore', 'renderingAnomalyScore', 'clientHintsInconsistencyScore', 'virtualizationScore']
1577
1544
  },
1578
1545
  distributed_botnets: {
1579
1546
  importance: 6.0,
1580
- ux_vs_security_ratio: 0.5, // Équilibré
1547
+ ux_vs_security_ratio: 0.5, // Balanced
1581
1548
  target_threshold: 'high',
1582
1549
  indicators: ['subnetScore', 'botnetClusterScore', 'ipReputationScore', 'tlsSpoofingScore']
1583
1550
  },
@@ -1585,11 +1552,11 @@ Optimization.Operators.createFullSecurityConfigEvaluator = (context) => {
1585
1552
  importance: 5.0,
1586
1553
  ux_vs_security_ratio: 0.4, // 40% FPR / 60% FNR
1587
1554
  target_threshold: 'medium',
1588
- indicators: ['botScore', 'tlsSpoofingScore', 'tcpAnomalyScore']
1555
+ indicators: ['botScore', 'tlsSpoofingScore', 'tcpAnomalyScore', 'virtualizationScore']
1589
1556
  }
1590
1557
  };
1591
1558
 
1592
- // Ancres immuables (Baseline Anchors) pour forcer le calibrage d'échelle de suspicion
1559
+ // Immutable baseline anchors to calibrate the suspicion scale
1593
1560
  const STATIC_ANCHORS = [
1594
1561
  {
1595
1562
  type: 'request_passed',
@@ -1600,7 +1567,8 @@ Optimization.Operators.createFullSecurityConfigEvaluator = (context) => {
1600
1567
  crossLayerInconsistencyScore: 0, timeInconsistencyScore: 0, tlsSpoofingScore: 0,
1601
1568
  clickVarianceScore: 0, clientHintsInconsistencyScore: 0, subnetScore: 0,
1602
1569
  ipReputationScore: 0, botnetClusterScore: 0, tcpAnomalyScore: 0,
1603
- quicAnomalyScore: 0, renderingAnomalyScore: 0, threatIntelScore: 0
1570
+ quicAnomalyScore: 0, renderingAnomalyScore: 0, threatIntelScore: 0,
1571
+ virtualizationScore: 0
1604
1572
  }
1605
1573
  },
1606
1574
  {
@@ -1612,11 +1580,12 @@ Optimization.Operators.createFullSecurityConfigEvaluator = (context) => {
1612
1580
  crossLayerInconsistencyScore: 80, timeInconsistencyScore: 100, tlsSpoofingScore: 100,
1613
1581
  clickVarianceScore: 100, clientHintsInconsistencyScore: 90, subnetScore: 100,
1614
1582
  ipReputationScore: 100, botnetClusterScore: 100, tcpAnomalyScore: 100,
1615
- quicAnomalyScore: 100, renderingAnomalyScore: 100, threatIntelScore: 100
1583
+ quicAnomalyScore: 100, renderingAnomalyScore: 100, threatIntelScore: 100,
1584
+ virtualizationScore: 100
1616
1585
  }
1617
1586
  },
1618
1587
  {
1619
- type: 'request_passed', // Profil humain sain avec du bruit (doit rester sous le seuil d'alerte, ex: < 20)
1588
+ type: 'request_passed', // Clean human profile with minor noise (must remain below alert threshold, e.g. < 20)
1620
1589
  weight: 10.0,
1621
1590
  vector: {
1622
1591
  historyScore: 15, rotationScore: 10, headerAnomalyScore: 20, requestPatternScore: 15,
@@ -1624,11 +1593,12 @@ Optimization.Operators.createFullSecurityConfigEvaluator = (context) => {
1624
1593
  crossLayerInconsistencyScore: 10, timeInconsistencyScore: 10, tlsSpoofingScore: 10,
1625
1594
  clickVarianceScore: 0, clientHintsInconsistencyScore: 10, subnetScore: 5,
1626
1595
  ipReputationScore: 5, botnetClusterScore: 5, tcpAnomalyScore: 10,
1627
- quicAnomalyScore: 10, renderingAnomalyScore: 5, threatIntelScore: 0
1596
+ quicAnomalyScore: 10, renderingAnomalyScore: 5, threatIntelScore: 0,
1597
+ virtualizationScore: 0
1628
1598
  }
1629
1599
  },
1630
1600
  {
1631
- type: 'challenge_issued', // Profil robot furtif moyen (doit être challengé, ex: > 35)
1601
+ type: 'challenge_issued', // Stealth automated bot profile (must be challenged, e.g. > 35)
1632
1602
  weight: 10.0,
1633
1603
  vector: {
1634
1604
  historyScore: 20, rotationScore: 20, headerAnomalyScore: 20, requestPatternScore: 40,
@@ -1636,7 +1606,8 @@ Optimization.Operators.createFullSecurityConfigEvaluator = (context) => {
1636
1606
  crossLayerInconsistencyScore: 20, timeInconsistencyScore: 20, tlsSpoofingScore: 30,
1637
1607
  clickVarianceScore: 20, clientHintsInconsistencyScore: 20, subnetScore: 15,
1638
1608
  ipReputationScore: 15, botnetClusterScore: 20, tcpAnomalyScore: 30,
1639
- quicAnomalyScore: 20, renderingAnomalyScore: 20, threatIntelScore: 0
1609
+ quicAnomalyScore: 20, renderingAnomalyScore: 20, threatIntelScore: 0,
1610
+ virtualizationScore: 30
1640
1611
  }
1641
1612
  }
1642
1613
  ];
@@ -1666,7 +1637,7 @@ Optimization.Operators.createFullSecurityConfigEvaluator = (context) => {
1666
1637
  trap_triggered: 2.0,
1667
1638
  };
1668
1639
 
1669
- // 1. Évaluation sur les données de trafic réelles
1640
+ // 1. Evaluate on real traffic logs
1670
1641
  for (const log of trafficData) {
1671
1642
  const weight = log.weight || 1.0;
1672
1643
  const confidence = (confidenceWeights[log.type] || 1.0) * weight;
@@ -1707,7 +1678,7 @@ Optimization.Operators.createFullSecurityConfigEvaluator = (context) => {
1707
1678
  }
1708
1679
 
1709
1680
  }
1710
- // 2. Évaluation sur les ancres immuables pour fixer l'échelle
1681
+ // 2. Evaluate against immutable baseline anchors to stabilize scale
1711
1682
  for (const anchor of STATIC_ANCHORS) {
1712
1683
  const score = calculateScore(anchor);
1713
1684
  const weight = anchor.weight;
@@ -1734,12 +1705,12 @@ Optimization.Operators.createFullSecurityConfigEvaluator = (context) => {
1734
1705
  if (isBot) {
1735
1706
  threatStats[threatName].totalBots += effectiveWeight;
1736
1707
  if (score < targetThreshold) {
1737
- threatStats[threatName].fn += effectiveWeight * 10; // Pénalité punitive forte
1708
+ threatStats[threatName].fn += effectiveWeight * 10; // Strict punitive penalty
1738
1709
  }
1739
1710
  } else {
1740
1711
  threatStats[threatName].totalHumans += effectiveWeight;
1741
1712
  if (score >= targetThreshold) {
1742
- threatStats[threatName].fp += effectiveWeight * 10; // Pénalité punitive forte
1713
+ threatStats[threatName].fp += effectiveWeight * 10; // Strict punitive penalty
1743
1714
  }
1744
1715
  }
1745
1716
  }
@@ -1767,7 +1738,7 @@ Optimization.Operators.createFullSecurityConfigEvaluator = (context) => {
1767
1738
  weightedFnr += fnr * importanceWeight * secRatio;
1768
1739
  }
1769
1740
 
1770
- // 3. Pénalité de dérive d'échelle L2 (Régularisation par rapport au point d'origine)
1741
+ // 3. L2 drift penalty against baseline configuration
1771
1742
  let regularizationPenalty = 0;
1772
1743
  if (currentConfig && currentConfig.weights) {
1773
1744
  for (const key in config.weights) {
@@ -1776,7 +1747,7 @@ Optimization.Operators.createFullSecurityConfigEvaluator = (context) => {
1776
1747
  }
1777
1748
  }
1778
1749
 
1779
- // 4. Maximisation de la marge de séparation (SVM-like Margin Loss)
1750
+ // 4. Maximizing separation margin (SVM-like Margin Loss)
1780
1751
  const marginOverlap = Math.max(0, maxHumanScore - minBotScore);
1781
1752
  const marginPenalty = marginOverlap / 100;
1782
1753
 
@@ -1787,10 +1758,10 @@ Optimization.Operators.createFullSecurityConfigEvaluator = (context) => {
1787
1758
  };
1788
1759
 
1789
1760
  /**
1790
- * Résout le problème de l'auto-tuning complet de la configuration de sécurité.
1791
- * @param {object} context - Le contexte contenant les données de trafic.
1792
- * @param {object} [options] - Options pour l'algorithme génétique.
1793
- * @returns {Array<{solution: object, objectives: number[]}>} Le front de Pareto des configurations optimales.
1761
+ * Solves the full security configuration auto-tuning problem.
1762
+ * @param {object} context - Context containing traffic event logs.
1763
+ * @param {object} [options] - Genetic algorithm options.
1764
+ * @returns {Array<{solution: object, objectives: number[]}>} Pareto front of optimal security configurations.
1794
1765
  */ // eslint-disable-line max-len
1795
1766
  Optimization.Operators.solveFullSecurityTuning = (context, options = {}) => {
1796
1767
  const fitnessFunction = Optimization.Operators.createFullSecurityConfigEvaluator(context);
@@ -1813,10 +1784,11 @@ Optimization.Operators.solveFullSecurityTuning = (context, options = {}) => {
1813
1784
  tcpAnomalyScore: 0.8,
1814
1785
  quicAnomalyScore: 0.8,
1815
1786
  renderingAnomalyScore: 0.8,
1816
- threatIntelScore: 1.0
1787
+ threatIntelScore: 1.0,
1788
+ virtualizationScore: 0.8
1817
1789
  };
1818
1790
 
1819
- // Un "individu" est un objet de configuration complet
1791
+ // An "individual" is a complete security configuration object
1820
1792
  const createIndividual = () => ({
1821
1793
  thresholds: {
1822
1794
  low: 15 + random() * 20, // 15-35
@@ -1847,10 +1819,10 @@ Optimization.Operators.solveFullSecurityTuning = (context, options = {}) => {
1847
1819
  }
1848
1820
  });
1849
1821
 
1850
- // Le crossover et la mutation doivent maintenant opérer sur des objets complexes.
1822
+ // Crossover and mutation operators for complex structured configurations
1851
1823
  const crossover = (c1, c2) => {
1852
1824
  const child = JSON.parse(JSON.stringify(c1)); // Deep copy
1853
- // Croisement pour chaque groupe de paramètres
1825
+ // Crossover across each parameter section
1854
1826
  for (const key in child.thresholds) {
1855
1827
  child.thresholds[key] = (c1.thresholds[key] + c2.thresholds[key]) / 2;
1856
1828
  }
@@ -1866,9 +1838,8 @@ Optimization.Operators.solveFullSecurityTuning = (context, options = {}) => {
1866
1838
  const mutate = (c, currentConfig) => {
1867
1839
  const newConfig = JSON.parse(JSON.stringify(c));
1868
1840
 
1869
- // --- NOUVELLE LOGIQUE : Sélection de section pondérée ---
1870
- // On donne plus de poids à la mutation des 'patterns' et des 'weights',
1871
- // car ils ont un impact plus direct sur la détection que les seuils.
1841
+ // Weighted section selection: prioritize 'patterns' and 'weights'
1842
+ // as they have a more direct impact on detection accuracy than thresholds.
1872
1843
  const sections = [
1873
1844
  { name: 'patterns', weight: 0.50 },
1874
1845
  { name: 'thresholds', weight: 0.25 },
@@ -1889,7 +1860,7 @@ Optimization.Operators.solveFullSecurityTuning = (context, options = {}) => {
1889
1860
  const keyToMutate = keys[crypto.randomInt(0, keys.length)];
1890
1861
 
1891
1862
 
1892
- // S'assurer que les valeurs restent dans des limites raisonnables
1863
+ // Ensure mutated parameters stay within reasonable bounds
1893
1864
  if (sectionToMutate === 'weights') {
1894
1865
  newConfig[sectionToMutate][keyToMutate] = Math.max(0.05, Math.min(1.5, newConfig[sectionToMutate][keyToMutate] + (random() - 0.5) * 0.1));
1895
1866
  } else if (sectionToMutate === 'thresholds') {
@@ -1902,10 +1873,10 @@ Optimization.Operators.solveFullSecurityTuning = (context, options = {}) => {
1902
1873
  }
1903
1874
  }
1904
1875
 
1905
- // Contrainte de dérive maximale (±30% par rapport à la configuration actuelle)
1876
+ // Maximum drift constraint (+/- 30% relative to current reference configuration)
1906
1877
  if (currentConfig && currentConfig[sectionToMutate] && currentConfig[sectionToMutate][keyToMutate] !== undefined) {
1907
1878
  const originalValue = currentConfig[sectionToMutate][keyToMutate];
1908
- if (typeof originalValue === 'number' && originalValue !== 0) { // Éviter la division par zéro ou la contrainte sur 0
1879
+ if (typeof originalValue === 'number' && originalValue !== 0) { // Avoid division by zero or locking zeroes
1909
1880
  const minAllowed = originalValue * 0.7; // -30%
1910
1881
  const maxAllowed = originalValue * 1.3; // +30%
1911
1882
  newConfig[sectionToMutate][keyToMutate] = Math.max(minAllowed, Math.min(maxAllowed, newConfig[sectionToMutate][keyToMutate]));
@@ -1919,7 +1890,7 @@ Optimization.Operators.solveFullSecurityTuning = (context, options = {}) => {
1919
1890
  createIndividual,
1920
1891
  fitnessFunction,
1921
1892
  crossover,
1922
- (c) => mutate(c, context.currentConfig), // Passer currentConfig à la fonction de mutation
1893
+ (c) => mutate(c, context.currentConfig), // Pass currentConfig to mutation function
1923
1894
  { generations: 50, populationSize: 50, ...options }
1924
1895
  );
1925
1896
  };