This commit is contained in:
2025-12-21 09:00:48 +01:00
parent fecdcafed2
commit cfb031aee6
4 changed files with 420 additions and 390 deletions

View File

@ -90,6 +90,47 @@ function formatNumber(num) {
return num.toLocaleString();
}
// =============================================================================
// Combination Calculator
// =============================================================================
function binomial(n, k) {
if (k > n) return 0;
if (k === 0 || k === n) return 1;
let result = 1;
for (let i = 0; i < k; i++) {
result = result * (n - i) / (i + 1);
}
return Math.round(result);
}
function calculateTotalCombinations(numCells, serial, parallel) {
const totalNeeded = serial * parallel;
if (numCells < totalNeeded) return 0;
let total = 1;
let remaining = numCells;
for (let i = 0; i < serial; i++) {
total *= binomial(remaining, parallel);
remaining -= parallel;
}
return total;
}
function updateMaxIterations() {
const serial = parseInt(DOM.cellsSerial.value) || 1;
const parallel = parseInt(DOM.cellsParallel.value) || 1;
const validCells = AppState.cells.filter(c => c.capacity && c.capacity > 0);
const totalCombinations = calculateTotalCombinations(validCells.length, serial, parallel);
// Set max iterations: use total combinations if reasonable, cap at 1,000,000
const suggested = Math.min(Math.max(totalCombinations, 1000), 1000000);
DOM.maxIterations.value = suggested;
}
// =============================================================================
// Configuration Management
// =============================================================================
@ -101,6 +142,7 @@ function updateConfigDisplay() {
DOM.configDisplay.textContent = `${serial}S${parallel}P`;
DOM.totalCellsNeeded.textContent = total;
updateMatchingButtonState();
updateMaxIterations();
}
// =============================================================================
@ -196,6 +238,8 @@ function updateCellStats() {
? `${Math.round(capacities.reduce((a, b) => a + b, 0) / capacities.length)} mAh` : '-';
DOM.statAvgIr.textContent = irs.length > 0
? `${(irs.reduce((a, b) => a + b, 0) / irs.length).toFixed(1)}` : '-';
updateMaxIterations();
}
function loadExampleData() {

View File

@ -232,10 +232,14 @@ class ExhaustiveSearch {
: [[...this.cells]];
for (const cellSubset of cellCombos) {
if (this.stopped) break;
if (this.stopped) {
return this.returnBestResult(iteration, totalCombinations);
}
for (const partition of this.generatePartitions(cellSubset, this.parallel, this.serial)) {
if (this.stopped) break;
if (this.stopped) {
return this.returnBestResult(iteration, totalCombinations);
}
const scoreResult = calculateScore(partition, this.capacityWeight, this.irWeight);
@ -247,7 +251,8 @@ class ExhaustiveSearch {
iteration++;
this.stats.recordIteration();
if (iteration % (this.maxIterations * 0.01) === 0) {
// Check frequently for stop and send progress updates every 100 iterations
if (iteration % 100 === 0) {
const stats = this.stats.getStats(iteration, Math.min(totalCombinations, this.maxIterations));
self.postMessage({
@ -265,12 +270,29 @@ class ExhaustiveSearch {
}
if (iteration >= this.maxIterations) {
this.stopped = true;
break;
return this.returnBestResult(iteration, totalCombinations);
}
}
}
return this.returnBestResult(iteration, totalCombinations);
}
returnBestResult(iteration, totalCombinations) {
if (!this.bestSolution) {
// No solution found yet, create one from first cells
const config = [];
for (let i = 0; i < this.serial; i++) {
config.push(this.cells.slice(i * this.parallel, (i + 1) * this.parallel));
}
const scoreResult = calculateScore(config, this.capacityWeight, this.irWeight);
this.bestSolution = { config, ...scoreResult };
this.bestScore = scoreResult.score;
}
const usedLabels = new Set(this.bestSolution.config.flat().map(c => c.label));
const excludedCells = this.cells.filter(c => !usedLabels.has(c.label));
// Final progress update
const stats = this.stats.getStats(iteration, Math.min(totalCombinations, this.maxIterations));
self.postMessage({
@ -286,9 +308,6 @@ class ExhaustiveSearch {
}
});
const usedLabels = new Set(this.bestSolution.config.flat().map(c => c.label));
const excludedCells = this.cells.filter(c => !usedLabels.has(c.label));
return {
configuration: this.bestSolution.config,
score: this.bestScore,