model-explorer / js /ui /modelComplexity.js
mr4's picture
Upload 71 files
9bd422a verified
/**
* ModelComplexity - Displays model complexity metrics
* Computes total parameters, memory footprint, and summary counts from parsed model data.
* Requirements: 20.1, 20.2, 20.3, 20.4, 20.5
*/
class ModelComplexity {
/**
* @param {string} containerId - ID of the container element
*/
constructor(containerId) {
this._containerId = containerId;
this._container = document.getElementById(containerId);
this._metrics = null;
if (!this._container) {
console.warn(`[ModelComplexity] Container #${containerId} not found`);
}
this._setupEventListeners();
}
// ─── Private ──────────────────────────────────────────────────────────────
/**
* Listen for model:loaded events to auto-update metrics.
*/
_setupEventListeners() {
if (window.EventBus && typeof CONFIG !== 'undefined' && CONFIG.EVENTS) {
window.EventBus.on(CONFIG.EVENTS.MODEL_LOADED, (data) => {
if (data && data.model) {
const metrics = this.compute(data.model);
this.render(metrics);
}
});
}
}
/**
* Format a parameter count into a human-readable string.
* Examples: 500 β†’ "500", 1500 β†’ "1.5K", 25600000 β†’ "25.6M", 1200000000 β†’ "1.2B"
* @param {number} count
* @returns {string}
*/
_formatParameterCount(count) {
if (count == null || isNaN(count) || count === 0) return '0';
const abs = Math.abs(count);
if (abs >= 1e9) {
return (count / 1e9).toFixed(1).replace(/\.0$/, '') + 'B';
}
if (abs >= 1e6) {
return (count / 1e6).toFixed(1).replace(/\.0$/, '') + 'M';
}
if (abs >= 1e3) {
return (count / 1e3).toFixed(1).replace(/\.0$/, '') + 'K';
}
return String(count);
}
// ─── Public API ───────────────────────────────────────────────────────────
/**
* Compute complexity metrics from a parsed model.
* @param {object} parsedModel
* @returns {{ totalParameters: number, memoryFootprint: number, totalNodes: number, totalEdges: number, totalInitializers: number }}
*/
compute(parsedModel) {
const nodes = (parsedModel && parsedModel.graph && parsedModel.graph.nodes) || [];
const edges = (parsedModel && parsedModel.graph && parsedModel.graph.edges) || [];
const initializers = (parsedModel && parsedModel.initializers) || [];
let totalParameters = 0;
let memoryFootprint = 0;
for (const init of initializers) {
totalParameters += (init.elementCount || 0);
memoryFootprint += (init.size || 0);
}
this._metrics = {
totalParameters,
memoryFootprint,
totalNodes: nodes.length,
totalEdges: edges.length,
totalInitializers: initializers.length,
};
return this._metrics;
}
/**
* Render the complexity metrics into the container.
* @param {{ totalParameters: number, memoryFootprint: number, totalNodes: number, totalEdges: number, totalInitializers: number }} metrics
*/
render(metrics) {
if (!this._container) return;
if (!metrics) {
this._container.innerHTML = '<p class="text-muted">No complexity metrics available.</p>';
return;
}
const { totalParameters, memoryFootprint, totalNodes, totalEdges, totalInitializers } = metrics;
const paramDisplay = this._formatParameterCount(totalParameters);
const memoryDisplay = (typeof Formatters !== 'undefined' && Formatters.formatBytes)
? Formatters.formatBytes(memoryFootprint)
: this._fallbackFormatBytes(memoryFootprint);
let html = `
<div class="mb-3">
<div class="d-flex flex-wrap gap-3 small">
<span><i class="fas fa-project-diagram me-1"></i><strong>${totalNodes}</strong> Nodes</span>
<span><i class="fas fa-arrows-alt-h me-1"></i><strong>${totalEdges}</strong> Edges</span>
<span><i class="fas fa-database me-1"></i><strong>${totalInitializers}</strong> Initializers</span>
</div>
</div>
<div class="d-flex flex-wrap gap-3">
<div class="card p-2 flex-fill text-center">
<div class="small text-muted">Parameters</div>
<div class="fw-bold">${paramDisplay === '0' ? '0 parameters' : paramDisplay}</div>
</div>
<div class="card p-2 flex-fill text-center">
<div class="small text-muted">Memory Footprint</div>
<div class="fw-bold">${memoryDisplay}</div>
</div>
</div>`;
this._container.innerHTML = html;
}
/**
* Fallback byte formatter when Formatters utility is not available.
* @param {number} bytes
* @returns {string}
*/
_fallbackFormatBytes(bytes) {
if (bytes == null || isNaN(bytes) || bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.min(Math.floor(Math.log(Math.abs(bytes)) / Math.log(k)), sizes.length - 1);
return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i];
}
/**
* Clear the display.
*/
clear() {
this._metrics = null;
if (!this._container) return;
this._container.innerHTML = '<p class="text-muted">Select a model to view complexity metrics</p>';
}
/**
* Get the last computed metrics.
* @returns {object|null}
*/
getMetrics() {
return this._metrics;
}
/**
* Get the formatted parameter count string (useful for testing).
* @param {number} count
* @returns {string}
*/
formatParameterCount(count) {
return this._formatParameterCount(count);
}
}
window.ModelComplexity = ModelComplexity;