diff --git a/README.md b/README.md index 49d616e..912f61f 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ This tool implements cell matching algorithms based on research findings about l > **Internal resistance matching for parallel-connected lithium-ion cells and impacts on battery pack cycle life** > -> Shi et al., Journal of Power Sources (2013) +> Wang et al., Journal of Power Sources (2013) > DOI: [10.1016/j.jpowsour.2013.11.064](https://doi.org/10.1016/j.jpowsour.2013.11.064) Key findings: @@ -60,14 +60,6 @@ Each cell requires: - **Capacity**: Measured capacity in mAh - **Internal Resistance** (optional): Measured IR in mΩ -### Algorithm Selection - -| Algorithm | Best For | Speed | -|-----------|----------|-------| -| Genetic Algorithm | Most cases, large pools | Fast | -| Simulated Annealing | Avoiding local optima | Medium | -| Exhaustive | Small configs (<8 cells) | Slow | - ### Matching Weights - **Capacity Weight**: Importance of matching parallel group capacities diff --git a/js/app.js b/js/app.js index 5a3ad37..e0aca92 100644 --- a/js/app.js +++ b/js/app.js @@ -368,11 +368,7 @@ function updateProgress(progress) { DOM.progressSpeed.textContent = `${formatNumber(Math.round(progress.iterationsPerSecond))}/s`; } - if (progress.eta > 0 && progress.eta < Infinity) { - DOM.progressEta.textContent = formatDuration(progress.eta); - } else if (progress.iteration >= progress.maxIterations * 0.9) { - DOM.progressEta.textContent = 'Almost done...'; - } + DOM.progressEta.textContent = formatDuration(progress.eta); if (progress.currentBest && progress.currentBest.config) { renderLivePreview(progress.currentBest.config); diff --git a/js/matching-worker.js b/js/matching-worker.js index 47d989e..a0dc619 100644 --- a/js/matching-worker.js +++ b/js/matching-worker.js @@ -102,36 +102,51 @@ function calculateScore(configuration, capacityWeight = 0.7, irWeight = 0.3) { class StatsTracker { constructor() { this.startTime = Date.now(); - this.iterationTimes = []; - this.lastIterationTime = this.startTime; - this.windowSize = 100; // Rolling window for time estimates - } - - recordIteration() { - const now = Date.now(); - const iterTime = now - this.lastIterationTime; - this.lastIterationTime = now; - - this.iterationTimes.push(iterTime); - if (this.iterationTimes.length > this.windowSize) { - this.iterationTimes.shift(); - } + this.lastProgressTime = this.startTime; + this.lastProgressIteration = 0; + this.speedHistory = []; + this.windowSize = 5; // Rolling window for speed averaging + this.lastEta = null; + this.lastEtaUpdate = 0; } getStats(currentIteration, maxIterations) { - const elapsedTime = Date.now() - this.startTime; - const avgIterTime = this.iterationTimes.length > 0 - ? this.iterationTimes.reduce((a, b) => a + b, 0) / this.iterationTimes.length + const now = Date.now(); + const elapsedTime = now - this.startTime; + + // Calculate speed based on iterations since last progress update + const iterationsDelta = currentIteration - this.lastProgressIteration; + const timeDelta = now - this.lastProgressTime; + + if (timeDelta > 0 && iterationsDelta > 0) { + const currentSpeed = (iterationsDelta / timeDelta) * 1000; // iterations per second + this.speedHistory.push(currentSpeed); + if (this.speedHistory.length > this.windowSize) { + this.speedHistory.shift(); + } + } + + this.lastProgressTime = now; + this.lastProgressIteration = currentIteration; + + // Average speed from history + const avgSpeed = this.speedHistory.length > 0 + ? this.speedHistory.reduce((a, b) => a + b, 0) / this.speedHistory.length : 0; const remainingIterations = maxIterations - currentIteration; - const eta = avgIterTime * remainingIterations; + const eta = avgSpeed > 0 ? (remainingIterations / avgSpeed) * 1000 : 0; + + // Only update ETA every 500ms to avoid flickering + if (now - this.lastEtaUpdate > 500 || this.lastEta === null) { + this.lastEta = eta; + this.lastEtaUpdate = now; + } return { elapsedTime, - avgIterTime, - eta, - iterationsPerSecond: avgIterTime > 0 ? 1000 / avgIterTime : 0 + eta: this.lastEta, + iterationsPerSecond: avgSpeed }; } } @@ -249,24 +264,28 @@ class ExhaustiveSearch { } iteration++; - this.stats.recordIteration(); - // Check frequently for stop and send progress updates every 100 iterations + // Check for stop every 100 iterations, but only send progress updates every 200ms if (iteration % 100 === 0) { - const stats = this.stats.getStats(iteration, Math.min(totalCombinations, this.maxIterations)); + const now = Date.now(); + const timeSinceLastProgress = now - this.stats.lastProgressTime; - self.postMessage({ - type: 'progress', - data: { - iteration, - maxIterations: Math.min(totalCombinations, this.maxIterations), - bestScore: this.bestScore, - currentBest: this.bestSolution, - totalCombinations, - evaluatedCombinations: iteration, - ...stats - } - }); + if (timeSinceLastProgress >= 200) { + const stats = this.stats.getStats(iteration, Math.min(totalCombinations, this.maxIterations)); + + self.postMessage({ + type: 'progress', + data: { + iteration, + maxIterations: Math.min(totalCombinations, this.maxIterations), + bestScore: this.bestScore, + currentBest: this.bestSolution, + totalCombinations, + evaluatedCombinations: iteration, + ...stats + } + }); + } } if (iteration >= this.maxIterations) {