@anonympins/fingerprint 0.7.3 → 0.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/README.md +1 -1
- package/package.json +109 -107
- package/public/fingerprint-anti-bot.zip +0 -0
- package/src/js/dynamic-wasm.js +218 -218
- package/src/js/fingerprint.client.js +329 -88
- package/src/js/fingerprint.client.obfuscated.js +1 -0
- package/src/js/fingerprint.js +486 -231
- package/src/js/fingerprint.worker.js +81 -0
- package/src/js/library.js +6 -2
- package/src/js/pow.solver.js +2 -3
- package/src/js/problem-manager.js +113 -113
- package/src/js/tests/dynamic-wasm.test.js +77 -77
- package/src/js/tests/fingerprint.client.init.test.js +108 -0
- package/src/js/tests/fingerprint.test.js +153 -3
- package/src/js/tests/threatIntel.test.js +40 -1
- package/src/php/Challenge/ChallengeUtils.php +25 -16
- package/src/php/Config/SecurityProfiles.php +5 -0
- package/src/php/FingerprintEngine.php +186 -37
- package/src/php/Tests/ChallengeUtilsTest.php +469 -453
- package/src/php/Tests/RequestUtilsTest.php +1 -1
- package/src/php/Tests/ThreatIntelTest.php +34 -0
- package/src/php/Utils/RequestUtils.php +257 -127
- package/src/php/WordPress/fingerprint-anti-bot.php +1 -1
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.mo +0 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.po +1 -1
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.mo +0 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.po +1 -1
- package/src/php/WordPress/readme.txt +1 -1
|
@@ -8,22 +8,22 @@ const __dirname = dirname(__filename);
|
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* @namespace FunctionRegistry
|
|
11
|
-
* @description
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* @description Registry to expose library functions in a controlled manner.
|
|
12
|
+
* Allows dynamic invocation from problem configurations.
|
|
13
|
+
* Uses dot notation to access nested functions (e.g. 'tsp.calculateEnergy').
|
|
14
14
|
*/
|
|
15
15
|
const FunctionRegistry = {};
|
|
16
16
|
|
|
17
|
-
// ---
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
FunctionRegistry['cpc.solve'] = Optimization.Operators.solveOptimalCPC; //
|
|
17
|
+
// --- Scoring functions (solution evaluation) ---
|
|
18
|
+
// These functions are adapters to use library utilities
|
|
19
|
+
// with the structure expected by the ProblemManager.
|
|
20
|
+
FunctionRegistry['cpc.solve'] = Optimization.Operators.solveOptimalCPC; // Register CPC solver
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
24
|
-
* @param {Array<{x: number, y: number}>} facilities -
|
|
25
|
-
* @param {object} payload -
|
|
26
|
-
* @returns {number}
|
|
23
|
+
* Evaluates total cost for the facility location problem.
|
|
24
|
+
* @param {Array<{x: number, y: number}>} facilities - Facilities.
|
|
25
|
+
* @param {object} payload - Payload containing customers and options.
|
|
26
|
+
* @returns {number} Total cost.
|
|
27
27
|
*/
|
|
28
28
|
FunctionRegistry['facility.calculateEnergy'] = (facilities, payload) => {
|
|
29
29
|
const customers = payload.customers || [];
|
|
@@ -44,50 +44,50 @@ FunctionRegistry['facility.calculateEnergy'] = (facilities, payload) => {
|
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
|
-
*
|
|
48
|
-
* @param {Array<{x: number, y: number}>} path -
|
|
49
|
-
* @returns {number}
|
|
47
|
+
* Evaluates total distance of a path for the traveling salesperson problem (TSP).
|
|
48
|
+
* @param {Array<{x: number, y: number}>} path - Array of points representing the path.
|
|
49
|
+
* @returns {number} Total path distance.
|
|
50
50
|
*/
|
|
51
51
|
FunctionRegistry['tsp.calculateEnergy'] = (path, payload) => {
|
|
52
52
|
const cities = payload?.cities || payload?.points || [];
|
|
53
53
|
if (path && typeof path[0] === 'number') {
|
|
54
54
|
return Optimization.Utils.evaluatePathDistance(cities, path);
|
|
55
55
|
}
|
|
56
|
-
// Fallback:
|
|
56
|
+
// Fallback: Creates an array of indices [0, 1, 2, ...] for evaluatePathDistance.
|
|
57
57
|
const indices = Array.from({ length: path.length }, (_, i) => i);
|
|
58
58
|
return Optimization.Utils.evaluatePathDistance(path, indices);
|
|
59
59
|
};
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* @param {Array<number>} weights -
|
|
66
|
-
* @param {object} payload -
|
|
67
|
-
* @returns {number}
|
|
62
|
+
* Evaluates portfolio metrics (return and volatility).
|
|
63
|
+
* Currently returns negative return to match the minimization objective
|
|
64
|
+
* of the library's genetic algorithm.
|
|
65
|
+
* @param {Array<number>} weights - Asset weights in the portfolio.
|
|
66
|
+
* @param {object} payload - Problem payload, containing assets.
|
|
67
|
+
* @returns {number} Negative return of the portfolio.
|
|
68
68
|
*/
|
|
69
69
|
FunctionRegistry['portfolio.calculateMetrics'] = (weights, payload) => {
|
|
70
70
|
const { assets, maxVolatility } = payload;
|
|
71
|
-
//
|
|
72
|
-
//
|
|
71
|
+
// Use the library operator to create the fitness function
|
|
72
|
+
// and invoke it immediately.
|
|
73
73
|
const fitnessFunction = Optimization.Operators.createPortfolioAllocator({
|
|
74
74
|
assets,
|
|
75
75
|
maxVolatility,
|
|
76
76
|
});
|
|
77
|
-
//
|
|
78
|
-
//
|
|
77
|
+
// Fitness function returns negative return, which is what we want
|
|
78
|
+
// to store as "energy" or score.
|
|
79
79
|
return fitnessFunction(weights);
|
|
80
80
|
};
|
|
81
81
|
|
|
82
|
-
// ---
|
|
83
|
-
//
|
|
82
|
+
// --- Solving functions (complete algorithms) ---
|
|
83
|
+
// Useful for workers running end-to-end tasks.
|
|
84
84
|
FunctionRegistry['tsp.solve'] = Optimization.Operators.solveTSP;
|
|
85
85
|
FunctionRegistry['portfolio.solve'] = Optimization.Operators.solvePortfolio;
|
|
86
|
-
FunctionRegistry['fraud.solve'] = Optimization.Operators.solveFraudDetection; //
|
|
86
|
+
FunctionRegistry['fraud.solve'] = Optimization.Operators.solveFraudDetection; // Register fraud solver
|
|
87
87
|
FunctionRegistry['facility.solve'] = Optimization.Operators.solveFacilityLocation;
|
|
88
88
|
FunctionRegistry['security.tune'] = Optimization.Operators.solveFullSecurityTuning;
|
|
89
89
|
|
|
90
|
-
// ---
|
|
90
|
+
// --- Utility functions ---
|
|
91
91
|
FunctionRegistry['utils.evaluatePathDistance'] = Optimization.Utils.evaluatePathDistance;
|
|
92
92
|
|
|
93
93
|
const yieldToEventLoop = () => new Promise(resolve => {
|
|
@@ -100,14 +100,14 @@ const yieldToEventLoop = () => new Promise(resolve => {
|
|
|
100
100
|
|
|
101
101
|
/**
|
|
102
102
|
* @namespace ProblemInitializers
|
|
103
|
-
* @description
|
|
103
|
+
* @description Functions to dynamically generate problem data.
|
|
104
104
|
*/
|
|
105
105
|
const ProblemInitializers = {
|
|
106
106
|
/**
|
|
107
|
-
*
|
|
108
|
-
* @param {object} params -
|
|
109
|
-
* @param {number} params.count -
|
|
110
|
-
* @param {{x: number, y: number}} [params.bounds={x: 1000, y: 1000}] -
|
|
107
|
+
* Generates a set of random points for a TSP problem.
|
|
108
|
+
* @param {object} params - Generation parameters.
|
|
109
|
+
* @param {number} params.count - Number of points to generate.
|
|
110
|
+
* @param {{x: number, y: number}} [params.bounds={x: 1000, y: 1000}] - Spatial bounds.
|
|
111
111
|
* @returns {Promise<Array<{x: number, y: number}>>}
|
|
112
112
|
*/
|
|
113
113
|
'generate:randomPoints': async (params) => {
|
|
@@ -125,9 +125,9 @@ const ProblemInitializers = {
|
|
|
125
125
|
},
|
|
126
126
|
|
|
127
127
|
/**
|
|
128
|
-
*
|
|
129
|
-
* @param {object} params -
|
|
130
|
-
* @param {number} params.count -
|
|
128
|
+
* Generates a set of random financial assets for a portfolio problem.
|
|
129
|
+
* @param {object} params - Generation parameters.
|
|
130
|
+
* @param {number} params.count - Number of assets to generate.
|
|
131
131
|
* @returns {Promise<Array<{expectedReturn: number, volatility: number}>>}
|
|
132
132
|
*/
|
|
133
133
|
'generate:randomAssets': async (params) => {
|
|
@@ -148,18 +148,18 @@ const ProblemInitializers = {
|
|
|
148
148
|
},
|
|
149
149
|
|
|
150
150
|
/**
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
* @param {object} params -
|
|
154
|
-
* @param {Array<any>} params.baseArgs -
|
|
155
|
-
* @param {object} params.variations -
|
|
156
|
-
* @returns {function(number): Array<any>}
|
|
151
|
+
* Creates a function that generates arguments for each worker in `runMultipleParallel`.
|
|
152
|
+
* Allows varying parameters (e.g. initial solution) for each cycle.
|
|
153
|
+
* @param {object} params - Configuration parameters.
|
|
154
|
+
* @param {Array<any>} params.baseArgs - Base arguments common to all workers.
|
|
155
|
+
* @param {object} params.variations - Describes how to vary an argument.
|
|
156
|
+
* @returns {function(number): Array<any>} The `workerDataGenerator` function.
|
|
157
157
|
*/
|
|
158
158
|
'generate:parallelArgs': (params) => {
|
|
159
159
|
const { baseArgs, variations } = params || {};
|
|
160
160
|
return (cycleIndex) => {
|
|
161
161
|
const cycleArgs = baseArgs ? [...baseArgs] : [];
|
|
162
|
-
//
|
|
162
|
+
// For now, handle initial solution variation for TSP
|
|
163
163
|
if (variations?.initialSolution === 'random') {
|
|
164
164
|
cycleArgs[0] = cycleArgs[0]?.sort(() => Math.random() - 0.5);
|
|
165
165
|
}
|
|
@@ -171,11 +171,11 @@ const ProblemInitializers = {
|
|
|
171
171
|
class ProblemManager {
|
|
172
172
|
/**
|
|
173
173
|
* @private
|
|
174
|
-
*
|
|
175
|
-
* @param {object} options -
|
|
176
|
-
* @param {string} [options.configPath] -
|
|
177
|
-
* @param {object} [options.config] -
|
|
178
|
-
* @param {Array<object>} problems -
|
|
174
|
+
* The constructor is private. Use the asynchronous factory method `create()`.
|
|
175
|
+
* @param {object} options - Initialization options.
|
|
176
|
+
* @param {string} [options.configPath] - Path to the configuration file.
|
|
177
|
+
* @param {object} [options.config] - Problem configuration object.
|
|
178
|
+
* @param {Array<object>} problems - Pre-loaded problems.
|
|
179
179
|
* @param {IStore} store - The datastore for synchronization.
|
|
180
180
|
*/
|
|
181
181
|
constructor(options, problems, store) {
|
|
@@ -187,8 +187,8 @@ class ProblemManager {
|
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
/**
|
|
190
|
-
*
|
|
191
|
-
* @param {object} options -
|
|
190
|
+
* Asynchronous factory method to create and initialize a ProblemManager instance.
|
|
191
|
+
* @param {object} options - Initialization options.
|
|
192
192
|
* @returns {Promise<ProblemManager>}
|
|
193
193
|
*/
|
|
194
194
|
static async create(options, store) {
|
|
@@ -233,22 +233,22 @@ class ProblemManager {
|
|
|
233
233
|
problem.state = storedState;
|
|
234
234
|
return problem;
|
|
235
235
|
}));
|
|
236
|
-
//
|
|
236
|
+
// Dynamic problem initialization
|
|
237
237
|
for (const problem of problems) {
|
|
238
|
-
//
|
|
238
|
+
// Resolve functions via registry
|
|
239
239
|
if (problem.workUnit && problem.workUnit.scoreFunction) {
|
|
240
240
|
problem.workUnit.scoreFunction = FunctionRegistry[problem.workUnit.scoreFunction] || null;
|
|
241
241
|
}
|
|
242
242
|
if (problem.payload) {
|
|
243
243
|
for (const key in problem.payload) {
|
|
244
244
|
const value = problem.payload[key];
|
|
245
|
-
//
|
|
245
|
+
// Look for an initialization instruction (e.g. { "$init": "generate:randomPoints", ... })
|
|
246
246
|
if (typeof value === 'object' && value !== null && value.$init) {
|
|
247
247
|
const initializer = ProblemInitializers[value.$init];
|
|
248
|
-
//
|
|
249
|
-
// Note:
|
|
248
|
+
// Look for a function instruction (e.g. { "$func": "tsp.calculateEnergy" })
|
|
249
|
+
// Note: Currently unused, but ready for future extension.
|
|
250
250
|
if (initializer) {
|
|
251
|
-
//
|
|
251
|
+
// Replace instruction object with dynamically generated data.
|
|
252
252
|
problem.payload[key] = await initializer(value.params || {});
|
|
253
253
|
}
|
|
254
254
|
}
|
|
@@ -263,8 +263,8 @@ class ProblemManager {
|
|
|
263
263
|
}
|
|
264
264
|
|
|
265
265
|
/**
|
|
266
|
-
*
|
|
267
|
-
* @param {number} suspicionFactor -
|
|
266
|
+
* Selects a problem and generates a work unit.
|
|
267
|
+
* @param {number} suspicionFactor - Suspicion factor to adjust difficulty.
|
|
268
268
|
* @returns {{problemId: string, task: object}|null}
|
|
269
269
|
*/
|
|
270
270
|
dispatchWork(suspicionFactor) {
|
|
@@ -278,7 +278,7 @@ class ProblemManager {
|
|
|
278
278
|
|
|
279
279
|
switch (problem.workUnit.type) {
|
|
280
280
|
case 'simulated_annealing_iterations':
|
|
281
|
-
//
|
|
281
|
+
// Ensure a minimum difficulty so the challenge is meaningful
|
|
282
282
|
const baseIterations = Math.max(15000, problem.workUnit.baseIterations || 0);
|
|
283
283
|
task.iterations = scalingFactor
|
|
284
284
|
? Math.floor(baseIterations * Math.pow(scalingFactor, suspicionFactor))
|
|
@@ -288,7 +288,7 @@ class ProblemManager {
|
|
|
288
288
|
break;
|
|
289
289
|
|
|
290
290
|
case 'genetic_algorithm_generations':
|
|
291
|
-
//
|
|
291
|
+
// Ensure a minimum difficulty so the challenge is meaningful
|
|
292
292
|
const baseGenerations = Math.max(50, problem.workUnit.baseGenerations || 0);
|
|
293
293
|
task.generations = scalingFactor
|
|
294
294
|
? Math.floor(baseGenerations * Math.pow(scalingFactor, suspicionFactor))
|
|
@@ -300,7 +300,7 @@ class ProblemManager {
|
|
|
300
300
|
case 'run_multiple_parallel':
|
|
301
301
|
task.solverName = problem.workUnit.solverName;
|
|
302
302
|
task.numCycles = problem.workUnit.numCycles;
|
|
303
|
-
//
|
|
303
|
+
// Arguments and generator are in payload for added flexibility
|
|
304
304
|
task.baseSolverArgs = problem.payload.baseSolverArgs;
|
|
305
305
|
task.workerDataGenerator = problem.payload.workerDataGenerator;
|
|
306
306
|
task.logProgress = problem.payload.logProgress || false;
|
|
@@ -308,15 +308,15 @@ class ProblemManager {
|
|
|
308
308
|
break;
|
|
309
309
|
|
|
310
310
|
case 'multi_objective_genetic_algorithm':
|
|
311
|
-
//
|
|
311
|
+
// Difficulty applies to the number of generations
|
|
312
312
|
const baseGenerationsMulti = Math.max(30, problem.workUnit.baseGenerations || 0);
|
|
313
313
|
task.generations = scalingFactor
|
|
314
314
|
? Math.floor(baseGenerationsMulti * Math.pow(scalingFactor, suspicionFactor))
|
|
315
315
|
: Math.floor(baseGenerationsMulti * (0.5 + suspicionFactor));
|
|
316
316
|
task.payload = problem.payload;
|
|
317
|
-
//
|
|
317
|
+
// Initial state is current Pareto front, which client can use for elitism
|
|
318
318
|
task.initialFront = problem.state.paretoFront;
|
|
319
|
-
task.solverName = problem.workUnit.solverName; //
|
|
319
|
+
task.solverName = problem.workUnit.solverName; // Name of solver to use (e.g. 'cpc.solve')
|
|
320
320
|
break;
|
|
321
321
|
|
|
322
322
|
case 'tfjs_learning':
|
|
@@ -330,9 +330,9 @@ class ProblemManager {
|
|
|
330
330
|
}
|
|
331
331
|
|
|
332
332
|
/**
|
|
333
|
-
*
|
|
334
|
-
* @param {string} problemId -
|
|
335
|
-
* @param {object} solutionData -
|
|
333
|
+
* Integrates a client's solution into the problem state.
|
|
334
|
+
* @param {string} problemId - Problem ID.
|
|
335
|
+
* @param {object} solutionData - Solution returned by client.
|
|
336
336
|
*/
|
|
337
337
|
async integrateSolution(problemId, solutionData) {
|
|
338
338
|
const problem = this.problems.find(p => p.id === problemId);
|
|
@@ -363,7 +363,7 @@ class ProblemManager {
|
|
|
363
363
|
return;
|
|
364
364
|
}
|
|
365
365
|
} catch (e) {}
|
|
366
|
-
// 1.
|
|
366
|
+
// 1. NEVER trust client score. Always recalculate.
|
|
367
367
|
const scoreFunction = problem.workUnit.scoreFunction;
|
|
368
368
|
if (!scoreFunction) {
|
|
369
369
|
console.error(`[ProblemManager] Aucune fonction de score définie pour ${problemId}. Impossible de vérifier la solution.`);
|
|
@@ -372,25 +372,25 @@ class ProblemManager {
|
|
|
372
372
|
const recalculatedEnergy = scoreFunction(solutionData.solution, problem.payload);
|
|
373
373
|
|
|
374
374
|
const currentBest = parseFloat(problem.state.bestEnergy) || Infinity;
|
|
375
|
-
// 2.
|
|
375
|
+
// 2. Compare recalculated score, not client score.
|
|
376
376
|
const isBetter = recalculatedEnergy < currentBest;
|
|
377
377
|
|
|
378
378
|
if (isBetter) {
|
|
379
379
|
problem.state.bestSolution = solutionData.solution;
|
|
380
|
-
problem.state.bestEnergy = recalculatedEnergy; // 3.
|
|
380
|
+
problem.state.bestEnergy = recalculatedEnergy; // 3. Store verified score.
|
|
381
381
|
problem.state.lastUpdate = new Date().toISOString();
|
|
382
382
|
console.log(`[ProblemManager] Nouvelle meilleure solution pour ${problemId}: ${recalculatedEnergy.toFixed(2)}`);
|
|
383
383
|
}
|
|
384
384
|
break;
|
|
385
385
|
case 'genetic_algorithm_generations':
|
|
386
|
-
//
|
|
387
|
-
const fitnessFunction = FunctionRegistry['portfolio.calculateMetrics']; //
|
|
386
|
+
// SAMPLING VERIFICATION to balance security and performance.
|
|
387
|
+
const fitnessFunction = FunctionRegistry['portfolio.calculateMetrics']; // Or a more generic function
|
|
388
388
|
if (!solutionData || !Array.isArray(solutionData.population)) {
|
|
389
389
|
console.error(`[ProblemManager] Propriété 'population' manquante ou invalide dans solutionData pour ${problemId}.`);
|
|
390
390
|
return;
|
|
391
391
|
}
|
|
392
392
|
if (solutionData.population.length === 0) {
|
|
393
|
-
return; //
|
|
393
|
+
return; // Graceful return if population is empty (e.g. redirect fallback)
|
|
394
394
|
}
|
|
395
395
|
if (!fitnessFunction) {
|
|
396
396
|
console.error(`[ProblemManager] Impossible de vérifier la population pour ${problemId} (fonction de fitness manquante).`);
|
|
@@ -407,14 +407,14 @@ class ProblemManager {
|
|
|
407
407
|
return;
|
|
408
408
|
}
|
|
409
409
|
}
|
|
410
|
-
// 1.
|
|
410
|
+
// 1. Choose a small random sample of the submitted population.
|
|
411
411
|
const sampleSize = Math.min(5, solutionData.population.length);
|
|
412
412
|
const sampleIndices = new Set();
|
|
413
413
|
while (sampleIndices.size < sampleSize) {
|
|
414
414
|
sampleIndices.add(Math.floor(Math.random() * solutionData.population.length));
|
|
415
415
|
}
|
|
416
416
|
|
|
417
|
-
// 2.
|
|
417
|
+
// 2. Recalculate score for this sample.
|
|
418
418
|
let totalRecalculatedFitness = 0;
|
|
419
419
|
for (const index of sampleIndices) {
|
|
420
420
|
const individual = solutionData.population[index];
|
|
@@ -426,14 +426,14 @@ class ProblemManager {
|
|
|
426
426
|
if (individual.fitness !== undefined && individual.fitness !== -1) {
|
|
427
427
|
if (Math.abs(individual.fitness - recalculated) > 1e-4) {
|
|
428
428
|
console.error(`[ProblemManager] Triche détectée pour ${problemId}! Fitness déclaré: ${individual.fitness}, recalculé: ${recalculated}`);
|
|
429
|
-
return; //
|
|
429
|
+
return; // Immediately reject entire population
|
|
430
430
|
}
|
|
431
431
|
}
|
|
432
|
-
individual.fitness = recalculated; //
|
|
432
|
+
individual.fitness = recalculated; // Enforce exact recalculated value
|
|
433
433
|
totalRecalculatedFitness += recalculated;
|
|
434
434
|
}
|
|
435
435
|
|
|
436
|
-
problem.state.population = solutionData.population; //
|
|
436
|
+
problem.state.population = solutionData.population; // Accept population
|
|
437
437
|
console.log(`[ProblemManager] Population mise à jour pour ${problemId}. Fitness moyen de l'échantillon: ${(totalRecalculatedFitness / sampleSize).toFixed(4)}`);
|
|
438
438
|
break;
|
|
439
439
|
|
|
@@ -442,7 +442,7 @@ class ProblemManager {
|
|
|
442
442
|
console.error(`[ProblemManager] Propriété 'paretoFront' manquante ou invalide dans solutionData pour ${problemId}.`);
|
|
443
443
|
return;
|
|
444
444
|
}
|
|
445
|
-
//
|
|
445
|
+
// For multi-objective, merge existing Pareto front with client's.
|
|
446
446
|
await this._integrateParetoFront(problem, solutionData.paretoFront);
|
|
447
447
|
break;
|
|
448
448
|
}
|
|
@@ -454,31 +454,31 @@ class ProblemManager {
|
|
|
454
454
|
}
|
|
455
455
|
|
|
456
456
|
/**
|
|
457
|
-
*
|
|
458
|
-
* @param {object} problem -
|
|
457
|
+
* Ensures a problem has an initial solution. If not, generates one.
|
|
458
|
+
* @param {object} problem - Problem object.
|
|
459
459
|
* @private
|
|
460
460
|
*/
|
|
461
461
|
async _ensureInitialSolution(problem) {
|
|
462
462
|
if (problem.state.bestSolution) {
|
|
463
|
-
return; //
|
|
463
|
+
return; // A solution already exists
|
|
464
464
|
}
|
|
465
465
|
|
|
466
466
|
console.log(`[ProblemManager] Génération d'une solution initiale pour le problème ${problem.id}...`);
|
|
467
467
|
|
|
468
|
-
//
|
|
468
|
+
// Use score function defined in config
|
|
469
469
|
const scoreFunction = problem.workUnit.scoreFunction;
|
|
470
|
-
//
|
|
470
|
+
// Assume initial solution source is defined in config
|
|
471
471
|
const initialSolutionSource = problem.payload[problem.workUnit.initialSolutionSource];
|
|
472
472
|
|
|
473
473
|
if (scoreFunction && initialSolutionSource && Array.isArray(initialSolutionSource)) {
|
|
474
474
|
const initialSolution = initialSolutionSource;
|
|
475
|
-
//
|
|
476
|
-
//
|
|
475
|
+
// Calculate score (energy, fitness, etc.) of this initial solution.
|
|
476
|
+
// Scoring function may require additional arguments from payload.
|
|
477
477
|
const score = scoreFunction(initialSolution, problem.payload);
|
|
478
478
|
|
|
479
479
|
problem.state.bestSolution = initialSolution;
|
|
480
|
-
//
|
|
481
|
-
problem.state.bestEnergy = score; //
|
|
480
|
+
// Score property name depends on problem type
|
|
481
|
+
problem.state.bestEnergy = score; // Could be generalized if needed
|
|
482
482
|
problem.state.lastUpdate = new Date().toISOString();
|
|
483
483
|
|
|
484
484
|
console.log(`[ProblemManager] Solution initiale pour ${problem.id} générée avec un score de ${score.toFixed(2)}.`);
|
|
@@ -488,9 +488,9 @@ class ProblemManager {
|
|
|
488
488
|
}
|
|
489
489
|
|
|
490
490
|
/**
|
|
491
|
-
*
|
|
492
|
-
* @param {object} problem -
|
|
493
|
-
* @param {Array<object>} newFront -
|
|
491
|
+
* Integrates a new Pareto front into problem state.
|
|
492
|
+
* @param {object} problem - Problem object.
|
|
493
|
+
* @param {Array<object>} newFront - Pareto front returned by client.
|
|
494
494
|
* @private
|
|
495
495
|
*/
|
|
496
496
|
async _integrateParetoFront(problem, newFront) {
|
|
@@ -499,13 +499,13 @@ class ProblemManager {
|
|
|
499
499
|
const currentFront = problem.state.paretoFront || []; // eslint-disable-line no-unused-vars
|
|
500
500
|
const combined = [...currentFront, ...newFront];
|
|
501
501
|
|
|
502
|
-
// ---
|
|
502
|
+
// --- Non-dominated sorting logic to find new best front ---
|
|
503
503
|
const paretoDominates = (a, b) => {
|
|
504
504
|
let aIsBetterInOne = false;
|
|
505
|
-
//
|
|
505
|
+
// Assume objectives are to be minimized
|
|
506
506
|
for (let i = 0; i < a.objectives.length; i++) {
|
|
507
|
-
if (a.objectives[i] > b.objectives[i]) return false; // A
|
|
508
|
-
if (a.objectives[i] < b.objectives[i]) aIsBetterInOne = true; // A
|
|
507
|
+
if (a.objectives[i] > b.objectives[i]) return false; // A is worse on at least one objective
|
|
508
|
+
if (a.objectives[i] < b.objectives[i]) aIsBetterInOne = true; // A is strictly better on at least one
|
|
509
509
|
}
|
|
510
510
|
return aIsBetterInOne;
|
|
511
511
|
};
|
|
@@ -543,33 +543,33 @@ class ProblemManager {
|
|
|
543
543
|
}
|
|
544
544
|
|
|
545
545
|
/**
|
|
546
|
-
*
|
|
547
|
-
* @param {string} [problemId] -
|
|
548
|
-
*
|
|
546
|
+
* Retrieves currently known best solution for one or more problems.
|
|
547
|
+
* @param {string} [problemId] - Optional problem ID to inspect.
|
|
548
|
+
* If omitted, returns best solutions for all problems.
|
|
549
549
|
* @returns {object|Array<object>|null}
|
|
550
|
-
* -
|
|
551
|
-
* -
|
|
550
|
+
* - If `problemId` is provided, returns `{ id, solution, score }` or `null` if not found.
|
|
551
|
+
* - If no `problemId` is provided, returns array of these objects.
|
|
552
552
|
*/
|
|
553
553
|
async getBestSolutions(problemId) {
|
|
554
554
|
const problemsToProcess = problemId
|
|
555
555
|
? this.problems.filter(p => p.id === problemId)
|
|
556
556
|
: this.problems;
|
|
557
557
|
|
|
558
|
-
//
|
|
558
|
+
// Only generate an initial solution for single-objective problems
|
|
559
559
|
for (const p of problemsToProcess.filter(p => p.workUnit.type !== 'multi_objective_genetic_algorithm')) {
|
|
560
560
|
await this._ensureInitialSolution(p);
|
|
561
561
|
}
|
|
562
562
|
|
|
563
563
|
const formatSolution = (p) => {
|
|
564
|
-
//
|
|
564
|
+
// After _ensureInitialSolution, p.state can be assumed to exist.
|
|
565
565
|
if (!p || !p.state) return null;
|
|
566
566
|
|
|
567
|
-
//
|
|
567
|
+
// Special case for multi-objective problems
|
|
568
568
|
if (p.workUnit.type === 'multi_objective_genetic_algorithm') {
|
|
569
569
|
return {
|
|
570
570
|
id: p.id,
|
|
571
|
-
solution: p.state.paretoFront, //
|
|
572
|
-
score: p.state.paretoFront?.length || 0, //
|
|
571
|
+
solution: p.state.paretoFront, // "solution" is the entire front
|
|
572
|
+
score: p.state.paretoFront?.length || 0, // "score" is number of points on front
|
|
573
573
|
lastUpdate: p.state.lastUpdate,
|
|
574
574
|
};
|
|
575
575
|
}
|
|
@@ -584,18 +584,18 @@ class ProblemManager {
|
|
|
584
584
|
|
|
585
585
|
if (problemId) {
|
|
586
586
|
const problem = this.problems.find(p => p.id === problemId);
|
|
587
|
-
return problem ? formatSolution(problem) : null; //
|
|
587
|
+
return problem ? formatSolution(problem) : null; // Initial filtering already done
|
|
588
588
|
}
|
|
589
589
|
|
|
590
|
-
//
|
|
590
|
+
// Return overview for all problems
|
|
591
591
|
return this.problems.map(formatSolution).filter(s => s && s.solution);
|
|
592
592
|
}
|
|
593
593
|
|
|
594
594
|
/**
|
|
595
|
-
*
|
|
596
|
-
* @param {string} problemId -
|
|
597
|
-
* @param {object} newPayload -
|
|
598
|
-
* @returns {boolean} - True
|
|
595
|
+
* Updates payload of a specific problem by its ID.
|
|
596
|
+
* @param {string} problemId - ID of problem to update.
|
|
597
|
+
* @param {object} newPayload - New payload object to replace old one.
|
|
598
|
+
* @returns {boolean} - True if update succeeded, false otherwise.
|
|
599
599
|
*/
|
|
600
600
|
async updateProblemPayload(problemId, newPayload) {
|
|
601
601
|
const problem = this.problems.find(p => p.id === problemId);
|
|
@@ -607,7 +607,7 @@ class ProblemManager {
|
|
|
607
607
|
console.log(`[ProblemManager] Mise à jour du payload pour le problème '${problemId}'.`);
|
|
608
608
|
problem.payload = newPayload;
|
|
609
609
|
|
|
610
|
-
//
|
|
610
|
+
// Invalidate current state because problem has changed
|
|
611
611
|
problem.state.bestSolution = null;
|
|
612
612
|
problem.state.bestEnergy = "Infinity";
|
|
613
613
|
|