Spaces:
Running
Running
Add weighted composite scores and average dataset ranks
Browse filesAdd positive one-decimal a/b/c weights summing to 1.0, Overall composite scores, mean per-dataset fractional ranks, and reproducible CSV fields. Document DBP kernel timing and float reconstruction limits. Measurement records and aggregates are unchanged.
- README.md +4 -1
- app.js +29 -10
- data.json +2 -2
- export_results.py +14 -3
- index.html +9 -2
- manifest.json +1 -1
- ranking.js +58 -0
- style.css +2 -0
- ui-data.json +0 -0
README.md
CHANGED
|
@@ -24,7 +24,7 @@ python3 deploy/huggingface-compressionbenchmark/export_results.py
|
|
| 24 |
python3 deploy/huggingface-compressionbenchmark/build_ui_payload.py
|
| 25 |
```
|
| 26 |
|
| 27 |
-
Publish `README.md`, `index.html`, `style.css`, `app.js`, `data.json`, `ui-data.json`, `manifest.json`, `export_results.py`, and `build_ui_payload.py` to the static Space. The manifest records the exact source-file identities and metric boundaries. The compact browser payload retains the full snapshot's measurements and SHA-256 identity.
|
| 28 |
|
| 29 |
Only measurements in the authoritative current project scope are exported. Missing metrics and missing coverage are displayed explicitly. Existing results do not imply independently verified round trips or uniform hardware environments unless those checks are recorded in the source.
|
| 30 |
|
|
@@ -34,7 +34,10 @@ Only measurements in the authoritative current project scope are exported. Missi
|
|
| 34 |
- Sortable metrics, optional columns, and CSV download of the selected ranking
|
| 35 |
- Compression rate versus encoding/decoding cost plots
|
| 36 |
- Dataset-level method details, snapshot provenance, and metric definitions
|
|
|
|
| 37 |
|
| 38 |
The default family view selects the best measured compression-rate configuration independently for each dataset. Switch to **Exact configuration** to hold a configuration fixed. Runtime results stay separate. The paper snapshot tab preserves the frozen source table and is separate from the current report aggregation.
|
| 39 |
|
|
|
|
|
|
|
| 40 |
Source project: https://github.com/xjz17/web_compression
|
|
|
|
| 24 |
python3 deploy/huggingface-compressionbenchmark/build_ui_payload.py
|
| 25 |
```
|
| 26 |
|
| 27 |
+
Publish `README.md`, `index.html`, `style.css`, `app.js`, `ranking.js`, `data.json`, `ui-data.json`, `manifest.json`, `export_results.py`, and `build_ui_payload.py` to the static Space. The manifest records the exact source-file identities and metric boundaries. The compact browser payload retains the full snapshot's measurements and SHA-256 identity.
|
| 28 |
|
| 29 |
Only measurements in the authoritative current project scope are exported. Missing metrics and missing coverage are displayed explicitly. Existing results do not imply independently verified round trips or uniform hardware environments unless those checks are recorded in the source.
|
| 30 |
|
|
|
|
| 34 |
- Sortable metrics, optional columns, and CSV download of the selected ranking
|
| 35 |
- Compression rate versus encoding/decoding cost plots
|
| 36 |
- Dataset-level method details, snapshot provenance, and metric definitions
|
| 37 |
+
- Overall composite score and average dataset Rank, with 36 valid positive one-decimal weight combinations
|
| 38 |
|
| 39 |
The default family view selects the best measured compression-rate configuration independently for each dataset. Switch to **Exact configuration** to hold a configuration fixed. Runtime results stay separate. The paper snapshot tab preserves the frozen source table and is separate from the current report aggregation.
|
| 40 |
|
| 41 |
+
The Overall score is `a × average rate + b × normalized average encode time + c × normalized average decode time`. Time normalization is linear min–max across all eligible rows in the selected runtime, scope and row mode; an equal-time range normalizes to zero. Search, family and coverage filters only hide rows and never change the reference. Average Rank averages per-dataset composite-score ranks with dataset-local time normalization and fractional ties. Missing results are omitted. Coverage and candidate counts can differ across datasets; method details expose dataset scores, ranks and candidate counts. CSV downloads include weights, normalization bounds and rank coverage. The default weights are `a=0.4, b=0.3, c=0.3`.
|
| 42 |
+
|
| 43 |
Source project: https://github.com/xjz17/web_compression
|
app.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
const $ = (id) => document.getElementById(id);
|
| 2 |
const escape = (value) => String(value ?? '').replace(/[&<>"']/g, (c) => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
| 3 |
const number = (v) => v != null && Number.isFinite(Number(v)) ? Number(v) : null;
|
|
@@ -6,10 +7,15 @@ const fmt = (v, digits=3) => number(v) === null ? 'Not reported' : Number(v).toL
|
|
| 6 |
const countFmt = (v) => Number(v).toLocaleString('en-US');
|
| 7 |
const runtimeName = (v) => ({cpp:'C++',java:'Java',python:'Python'}[v] || v);
|
| 8 |
const cols = [
|
|
|
|
|
|
|
|
|
|
| 9 |
{key:'compression_rate',label:'Average rate ↓',digits:4,visible:true},
|
| 10 |
{key:'overall_rate',label:'Overall rate ↓',digits:4,visible:false},
|
| 11 |
{key:'compression_time',label:'Encode ns/point ↓',digits:2,visible:true},
|
| 12 |
{key:'decompression_time',label:'Decode ns/point ↓',digits:2,visible:true},
|
|
|
|
|
|
|
| 13 |
{key:'compressed_bytes',label:'Compressed bytes',digits:0,visible:false},
|
| 14 |
{key:'original_bytes',label:'Original bytes',digits:0,visible:false},
|
| 15 |
];
|
|
@@ -44,7 +50,8 @@ function aggregate(input) {
|
|
| 44 |
const original=hasBytes?source.reduce((s,r)=>s+r.original_bytes,0):null, compressed=hasBytes?source.reduce((s,r)=>s+r.compressed_bytes,0):null;
|
| 45 |
rows.push({method,family:source[0].family,runtime:source[0].runtime,compression_rate:mean(source.map(r=>r.compression_rate)),compression_time:mean(source.map(r=>r.compression_time)),decompression_time:mean(source.map(r=>r.decompression_time)),overall_rate:hasBytes?compressed/original:null,original_bytes:original,compressed_bytes:compressed,coverage:new Set(source.map(r=>r.dataset)).size,totalDatasets,source});
|
| 46 |
}
|
| 47 |
-
|
|
|
|
| 48 |
const av=number(a[sort.key]),bv=number(b[sort.key]);
|
| 49 |
if(av===null&&bv===null)return a.method.localeCompare(b.method);
|
| 50 |
if(av===null)return 1;if(bv===null)return -1;
|
|
@@ -52,10 +59,10 @@ function aggregate(input) {
|
|
| 52 |
});
|
| 53 |
}
|
| 54 |
function renderTable() {
|
| 55 |
-
const visible=cols.filter(c=>c.visible);
|
| 56 |
$('leaderboard').querySelector('thead').innerHTML=`<tr><th>Position</th><th>Method / configuration</th>${visible.map(c=>`<th aria-sort="${sort.key===c.key?(sort.direction===1?'ascending':'descending'):'none'}"><button data-sort="${c.key}" title="Sort ${escape(c.label)}">${escape(c.label)}${sort.key===c.key?(sort.direction===1?' ▴':' ▾'):''}</button></th>`).join('')}<th>Dataset coverage</th></tr>`;
|
| 57 |
const minima=Object.fromEntries(visible.map(c=>[c.key,Math.min(...currentRows.map(r=>number(r[c.key])).filter(n=>n!==null))]));
|
| 58 |
-
$('leaderboard').querySelector('tbody').innerHTML=currentRows.length?currentRows.map((r,i)=>`<tr><td class="rank numeric">${i+1}</td><td><button class="method-button" data-method="${escape(r.method)}">${escape(r.method)}</button><span class="method-family">${escape(r.family)} · ${escape(runtimeName(r.runtime))}</span></td>${visible.map(c=>`<td class="numeric ${!c.key
|
| 59 |
for(const button of $('leaderboard').querySelectorAll('[data-sort]'))button.addEventListener('click',()=>{const key=button.dataset.sort;sort={key,direction:sort.key===key?-sort.direction:1};refresh();});
|
| 60 |
for(const button of $('leaderboard').querySelectorAll('[data-method]'))button.addEventListener('click',()=>showMethod(button.dataset.method,true));
|
| 61 |
$('row-count').textContent=`${currentRows.length} ${state().mode==='family'?'method families':'method configurations'} · sorted by ${cols.find(c=>c.key===sort.key)?.label??sort.key}`;
|
|
@@ -69,7 +76,12 @@ function refresh() {
|
|
| 69 |
// preaggregated fixed/non-fixed metrics and call it an all-float result.
|
| 70 |
$('results-note').textContent='All-float measurements are not available in this snapshot. Select a precision category.';
|
| 71 |
} else $('results-note').textContent=state().mode==='family'?'Each family uses its lowest-rate measured configuration per dataset. Configurations may differ across datasets; use Exact configuration for fixed parameters.':'Each row retains one exact configuration. Lower compression rates and lower ns/point are better. Coverage counts datasets, not identical column sets.';
|
| 72 |
-
currentRows=aggregate(filteredRecords());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
const s=state(),scope=scopeKey(),ds=s.dataset==='all'?'all measured datasets':s.dataset;
|
| 74 |
const names={overall:'all numeric columns',integer:'integer columns',float:'floating-point columns',fixed_float:'fixed-precision float columns',nonfixed_float:'non-fixed-precision float columns'};
|
| 75 |
$('selection-summary').textContent=`${runtimeName(s.runtime)} · ${names[scope]||scope} · ${ds} · ${currentRows.length} methods`;
|
|
@@ -82,6 +94,9 @@ function setTab(tab) {
|
|
| 82 |
activeTab=['overall','dataset','tradeoffs','methods','paper','about'].includes(tab)?tab:'overall';
|
| 83 |
for(const b of document.querySelectorAll('[data-tab]')){b.classList.toggle('active',b.dataset.tab===activeTab);if(b.dataset.tab===activeTab)b.setAttribute('aria-current','page');else b.removeAttribute('aria-current');}
|
| 84 |
$('results-view').hidden=['methods','paper','about'].includes(activeTab);$('methods-view').hidden=activeTab!=='methods';$('paper-view').hidden=activeTab!=='paper';$('about-view').hidden=activeTab!=='about';$('chart-view').hidden=activeTab!=='tradeoffs';
|
|
|
|
|
|
|
|
|
|
| 85 |
if(activeTab==='dataset'&&$('dataset').value==='all'){const first=[...datasets.keys()].find(k=>records.some(r=>r.dataset===k&&r.runtime===$('runtime').value));if(first)$('dataset').value=first;}
|
| 86 |
if(activeTab==='overall')$('dataset').value='all';
|
| 87 |
refresh();
|
|
@@ -89,9 +104,9 @@ function setTab(tab) {
|
|
| 89 |
function showMethod(method,focus=false) {
|
| 90 |
const row=currentRows.find(r=>r.method===method), detail=$('method-detail');
|
| 91 |
if(!row){selectedMethod=null;detail.hidden=true;return;}selectedMethod=method;
|
| 92 |
-
const meta=methods.get(method)??methods.get(row.source[0].configuration), pipeline=meta?.operator_pipeline;
|
| 93 |
const source=row.source.slice().sort((a,b)=>a.dataset.localeCompare(b.dataset));
|
| 94 |
-
detail.innerHTML=`<div class="detail-head"><div><h2 id="detail-title">${escape(method)}</h2><p class="muted">${escape(row.family)} · ${escape(runtimeName(row.runtime))} · ${row.coverage} measured datasets</p></div><button id="close-detail" class="quiet-button">Close details</button></div><div class="detail-grid"><div><strong>${fmt(row.compression_rate,4)}</strong><span>average compression rate</span></div><div><strong>${fmt(row.compression_time,2)}</strong><span>encoding ns/point</span></div><div><strong>${fmt(row.decompression_time,2)}</strong><span>decoding ns/point</span></div><div><strong>${row.coverage} / ${row.totalDatasets}</strong><span>dataset coverage</span></div></div>${pipeline?`<p class="source-code">Interpretive operator mapping: ${escape(typeof pipeline==='string'?pipeline:JSON.stringify(pipeline))}</p>`:''}<p class="muted">Dataset metrics below come from the published result files. Independent round-trip evidence and hardware are ${snapshot.benchmark?.roundtrip_evidence?'documented in the manifest':'not reported for this snapshot'}.</p><div class="detail-table"><table><thead><tr><th>Dataset</th><th>Configuration</th><th>Rate ↓</th><th>Encode ns/point ↓</th><th>Decode ns/point ↓</th><th>Original bytes</th><th>Compressed bytes</th></tr></thead><tbody>${source.map(r=>`<tr><td>${escape(r.dataset)}</td><td>${escape(r.configuration)}</td><td class="numeric">${fmt(r.compression_rate,4)}</td><td class="numeric">${fmt(r.compression_time,2)}</td><td class="numeric">${fmt(r.decompression_time,2)}</td><td class="numeric">${fmt(r.original_bytes,0)}</td><td class="numeric">${fmt(r.compressed_bytes,0)}</td></tr>`).join('')}</tbody></table></div>`;
|
| 95 |
detail.hidden=false;$('close-detail').addEventListener('click',()=>{detail.hidden=true;selectedMethod=null;});
|
| 96 |
if(focus){detail.scrollIntoView({block:'nearest',behavior:'auto'});detail.focus({preventScroll:true});}
|
| 97 |
}
|
|
@@ -113,14 +128,16 @@ function renderChart() {
|
|
| 113 |
}
|
| 114 |
function csv() {
|
| 115 |
const quote=v=>{let text=String(v??'');if(typeof v==='string'&&/^[=+@-]/.test(text))text="'"+text;return `"${text.replace(/"/g,'""')}"`;};
|
| 116 |
-
const
|
| 117 |
-
const
|
|
|
|
| 118 |
const url=URL.createObjectURL(new Blob(['\ufeff'+[columns.join(','),...body].join('\r\n')],{type:'text/csv;charset=utf-8'}));const a=document.createElement('a');a.href=url;a.download=`THULab-compression-${state().runtime}-${scopeKey()}-${state().dataset}.csv`;a.click();setTimeout(()=>URL.revokeObjectURL(url),1000);
|
| 119 |
}
|
| 120 |
function renderAbout() {
|
| 121 |
const b=snapshot.benchmark||{},s=snapshot.source||{},c=snapshot.coverage||{};
|
| 122 |
const notes=[b.scope_note,b.aggregation_note,b.selection_note,...(b.notes||[]),...(b.measurement_boundaries||[])].filter(Boolean);
|
| 123 |
$('methodology').innerHTML=notes.map(n=>`<p class="notice">${escape(n)}</p>`).join('');
|
|
|
|
| 124 |
const definitions=b.metric_definitions||{};if(Object.keys(definitions).length)$('methodology').innerHTML+=`<dl class="definitions">${Object.entries(definitions).map(([k,v])=>`<div><dt>${escape(k.replaceAll('_',' '))}</dt><dd>${escape(typeof v==='string'?v:JSON.stringify(v))}</dd></div>`).join('')}</dl>`;
|
| 125 |
const exclusions=c.exclusions||c.excluded_datasets||snapshot.exclusions||[];
|
| 126 |
const info={ 'Snapshot exported':snapshot.exported_at||s.exported_at||'Not reported','Dataset scope generated':s.generated_at||'Not reported', 'Source checkout':s.git_commit||'Not reported','Source files':`${s.source_files?.length??0} metadata inputs and ${s.result_csv_files_used?.length??0} result CSVs (hashes in manifest)`,'Result entries across scopes':`${records.length} (scope slices can reuse a measurement)`,'Worktree source state':s.worktree_dirty_inputs?.length?'Includes current local changes; input file hashes identify the published snapshot.':'See manifest for source file hashes.','Runtime environment':b.hardware||'Hardware details are not recorded in this exported result snapshot.','Lossless evidence':b.roundtrip_evidence||'Lossless-design method filter from the source project; per-run round-trip verification is not recorded here.'};
|
|
@@ -141,9 +158,11 @@ function init() {
|
|
| 141 |
$('dataset-count').textContent=countFmt(new Set(records.map(r=>r.dataset)).size);$('method-count').textContent=countFmt(new Set(records.map(r=>r.family)).size);$('runtime-count').textContent=runtimes.length;$('record-count').textContent=countFmt(snapshot.coverage?.runtime_configuration_count??new Set(records.map(r=>`${r.runtime}\0${r.method}`)).size);
|
| 142 |
const exported=snapshot.exported_at||snapshot.source?.exported_at||'not reported';$('snapshot').textContent=`Result snapshot exported · ${String(exported).replace('T',' ').slice(0,19)}${exported==='not reported'?'':' UTC'}`;$('footer-snapshot').textContent=`Source inputs documented in the manifest`;
|
| 143 |
$('column-options').innerHTML=cols.map(c=>`<label><input type="checkbox" data-column="${c.key}" ${c.visible?'checked':''}>${escape(c.label)}</label>`).join('');
|
|
|
|
|
|
|
| 144 |
for(const input of $('column-options').querySelectorAll('input'))input.addEventListener('change',()=>{cols.find(c=>c.key===input.dataset.column).visible=input.checked;renderTable();});
|
| 145 |
for(const id of ['runtime','dtype','precision','dataset','family','row-mode','complete-only'])$(id).addEventListener('change',refresh);$('search').addEventListener('input',refresh);$('chart-metric').addEventListener('change',renderChart);$('download').addEventListener('click',csv);
|
| 146 |
-
$('reset').addEventListener('click',()=>{$('runtime').value=runtimes.includes('cpp')?'cpp':runtimes[0];$('dtype').value='overall';$('precision').value='all';$('dataset').value='all';$('row-mode').value='family';$('search').value='';$('family').value='all';$('complete-only').checked=false;selectedMethod=null;$('method-detail').hidden=true;sort={key:'compression_rate',direction:1};setTab(activeTab);});
|
| 147 |
for(const tab of document.querySelectorAll('[data-tab]'))tab.addEventListener('click',()=>{history.replaceState(null,'',`#${tab.dataset.tab}`);setTab(tab.dataset.tab);});
|
| 148 |
const renderPaper=()=>{const rows=(snapshot.paper_table3?.rows||[]).filter(r=>r.scope===$('paper-scope').value);$('paper-table').querySelector('tbody').innerHTML=rows.map(r=>`<tr><td>${escape(r.method)}</td><td class="numeric">${fmt(r.rank,1)}</td><td class="numeric">${fmt(r.average_compression_rate,4)}</td><td class="numeric">${fmt(r.average_compression_time_ns_per_point,1)}</td><td class="numeric">${fmt(r.average_decompression_time_ns_per_point,1)}</td><td class="numeric">${fmt(r.median_balanced_score,4)}</td></tr>`).join('');};$('paper-scope').addEventListener('change',renderPaper);renderPaper();
|
| 149 |
renderAbout();setTab(location.hash.slice(1)||'overall');
|
|
@@ -157,5 +176,5 @@ function setupTheme() {
|
|
| 157 |
$('theme').addEventListener('click',()=>{const next=document.documentElement.dataset.theme==='dark'?'light':'dark';document.documentElement.dataset.theme=next;$('theme').textContent=next==='dark'?'Light mode':'Dark mode';try{localStorage.setItem('thulab-theme',next);}catch{}});
|
| 158 |
}
|
| 159 |
setupTheme();
|
| 160 |
-
try { const response=await fetch('./ui-data.json');if(!response.ok)throw new Error(`Snapshot request returned ${response.status}`);snapshot=await response.json();init(); }
|
| 161 |
catch(e){$('load-error').hidden=false;$('load-error').textContent=`The result snapshot could not be loaded. Please reload the page or download the JSON from the repository. ${e.message}`;$('snapshot').textContent='Snapshot unavailable';$('leaderboard').querySelector('tbody').innerHTML='<tr><td class="empty-cell">Results unavailable.</td></tr>';}
|
|
|
|
| 1 |
+
import {WEIGHT_TENTHS,parseWeights,addRankingMetrics} from './ranking.js?v=20261008-rank-v1';
|
| 2 |
const $ = (id) => document.getElementById(id);
|
| 3 |
const escape = (value) => String(value ?? '').replace(/[&<>"']/g, (c) => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
| 4 |
const number = (v) => v != null && Number.isFinite(Number(v)) ? Number(v) : null;
|
|
|
|
| 7 |
const countFmt = (v) => Number(v).toLocaleString('en-US');
|
| 8 |
const runtimeName = (v) => ({cpp:'C++',java:'Java',python:'Python'}[v] || v);
|
| 9 |
const cols = [
|
| 10 |
+
{key:'average_rank',label:'Average Rank ↓',digits:2,visible:true,overallOnly:true},
|
| 11 |
+
{key:'rank_coverage',label:'Rank coverage',digits:0,visible:false,overallOnly:true},
|
| 12 |
+
{key:'composite_score',label:'Composite score ↓',digits:4,visible:true,overallOnly:true},
|
| 13 |
{key:'compression_rate',label:'Average rate ↓',digits:4,visible:true},
|
| 14 |
{key:'overall_rate',label:'Overall rate ↓',digits:4,visible:false},
|
| 15 |
{key:'compression_time',label:'Encode ns/point ↓',digits:2,visible:true},
|
| 16 |
{key:'decompression_time',label:'Decode ns/point ↓',digits:2,visible:true},
|
| 17 |
+
{key:'normalized_compression_time',label:'Normalized encode ↓',digits:4,visible:false,overallOnly:true},
|
| 18 |
+
{key:'normalized_decompression_time',label:'Normalized decode ↓',digits:4,visible:false,overallOnly:true},
|
| 19 |
{key:'compressed_bytes',label:'Compressed bytes',digits:0,visible:false},
|
| 20 |
{key:'original_bytes',label:'Original bytes',digits:0,visible:false},
|
| 21 |
];
|
|
|
|
| 50 |
const original=hasBytes?source.reduce((s,r)=>s+r.original_bytes,0):null, compressed=hasBytes?source.reduce((s,r)=>s+r.compressed_bytes,0):null;
|
| 51 |
rows.push({method,family:source[0].family,runtime:source[0].runtime,compression_rate:mean(source.map(r=>r.compression_rate)),compression_time:mean(source.map(r=>r.compression_time)),decompression_time:mean(source.map(r=>r.decompression_time)),overall_rate:hasBytes?compressed/original:null,original_bytes:original,compressed_bytes:compressed,coverage:new Set(source.map(r=>r.dataset)).size,totalDatasets,source});
|
| 52 |
}
|
| 53 |
+
const s=state();
|
| 54 |
+
return addRankingMetrics(rows,parseWeights($('weights').value)).filter(r=>(s.family==='all'||r.family===s.family)&&(!s.search||(s.mode==='family'?r.family:`${r.method} ${r.family}`).toLowerCase().includes(s.search))&&(!s.complete||r.coverage===totalDatasets)).sort((a,b)=>{
|
| 55 |
const av=number(a[sort.key]),bv=number(b[sort.key]);
|
| 56 |
if(av===null&&bv===null)return a.method.localeCompare(b.method);
|
| 57 |
if(av===null)return 1;if(bv===null)return -1;
|
|
|
|
| 59 |
});
|
| 60 |
}
|
| 61 |
function renderTable() {
|
| 62 |
+
const visible=cols.filter(c=>c.visible&&(!c.overallOnly||activeTab==='overall'));
|
| 63 |
$('leaderboard').querySelector('thead').innerHTML=`<tr><th>Position</th><th>Method / configuration</th>${visible.map(c=>`<th aria-sort="${sort.key===c.key?(sort.direction===1?'ascending':'descending'):'none'}"><button data-sort="${c.key}" title="Sort ${escape(c.label)}">${escape(c.label)}${sort.key===c.key?(sort.direction===1?' ▴':' ▾'):''}</button></th>`).join('')}<th>Dataset coverage</th></tr>`;
|
| 64 |
const minima=Object.fromEntries(visible.map(c=>[c.key,Math.min(...currentRows.map(r=>number(r[c.key])).filter(n=>n!==null))]));
|
| 65 |
+
$('leaderboard').querySelector('tbody').innerHTML=currentRows.length?currentRows.map((r,i)=>`<tr><td class="rank numeric">${i+1}</td><td><button class="method-button" data-method="${escape(r.method)}">${escape(r.method)}</button><span class="method-family">${escape(r.family)} · ${escape(runtimeName(r.runtime))}</span>${snapshot.benchmark?.method_audits?.[r.family]?`<span class="audit-badge">${escape(snapshot.benchmark.method_audits[r.family].label)}</span>`:''}</td>${visible.map(c=>`<td class="numeric ${!['compressed_bytes','original_bytes','rank_coverage'].includes(c.key)&&number(r[c.key])!==null&&r[c.key]===minima[c.key]?'best':''}">${c.key==='rank_coverage'?`${r.rank_coverage} / ${r.totalDatasets}`:fmt(r[c.key],c.digits)}</td>`).join('')}<td><span class="coverage ${r.coverage===r.totalDatasets?'full':''}">${r.coverage} / ${r.totalDatasets}</span></td></tr>`).join(''):`<tr><td colspan="${visible.length+3}" class="empty-cell">No measurements match this selection. Try another runtime, data type, or dataset.</td></tr>`;
|
| 66 |
for(const button of $('leaderboard').querySelectorAll('[data-sort]'))button.addEventListener('click',()=>{const key=button.dataset.sort;sort={key,direction:sort.key===key?-sort.direction:1};refresh();});
|
| 67 |
for(const button of $('leaderboard').querySelectorAll('[data-method]'))button.addEventListener('click',()=>showMethod(button.dataset.method,true));
|
| 68 |
$('row-count').textContent=`${currentRows.length} ${state().mode==='family'?'method families':'method configurations'} · sorted by ${cols.find(c=>c.key===sort.key)?.label??sort.key}`;
|
|
|
|
| 76 |
// preaggregated fixed/non-fixed metrics and call it an all-float result.
|
| 77 |
$('results-note').textContent='All-float measurements are not available in this snapshot. Select a precision category.';
|
| 78 |
} else $('results-note').textContent=state().mode==='family'?'Each family uses its lowest-rate measured configuration per dataset. Configurations may differ across datasets; use Exact configuration for fixed parameters.':'Each row retains one exact configuration. Lower compression rates and lower ns/point are better. Coverage counts datasets, not identical column sets.';
|
| 79 |
+
currentRows=aggregate(filteredRecords(true));
|
| 80 |
+
const audit=snapshot.benchmark?.method_audits?.DBP;
|
| 81 |
+
$('audit-notice').hidden=!audit||!currentRows.some(r=>r.family==='DBP');
|
| 82 |
+
$('audit-notice').textContent=audit?'DBP measurement audit: '+audit.timing+' '+audit.storage+' Float end-to-end losslessness is not established. See method details.':'';
|
| 83 |
+
const weights=parseWeights($('weights').value);
|
| 84 |
+
$('score-note').textContent=`Score = ${weights[0].toFixed(1)} × average rate + ${weights[1].toFixed(1)} × normalized encode + ${weights[2].toFixed(1)} × normalized decode. Lower is better. Time normalization: (time − minimum) / (maximum − minimum), across all measured rows in this runtime, scope and row mode. Search and visibility filters keep the reference fixed. Average Rank: mean of per-dataset score ranks with dataset-local time normalization; ties share their average position. Missing results are omitted. Dataset candidate counts differ, so compare rank coverage and inspect method details.`;
|
| 85 |
const s=state(),scope=scopeKey(),ds=s.dataset==='all'?'all measured datasets':s.dataset;
|
| 86 |
const names={overall:'all numeric columns',integer:'integer columns',float:'floating-point columns',fixed_float:'fixed-precision float columns',nonfixed_float:'non-fixed-precision float columns'};
|
| 87 |
$('selection-summary').textContent=`${runtimeName(s.runtime)} · ${names[scope]||scope} · ${ds} · ${currentRows.length} methods`;
|
|
|
|
| 94 |
activeTab=['overall','dataset','tradeoffs','methods','paper','about'].includes(tab)?tab:'overall';
|
| 95 |
for(const b of document.querySelectorAll('[data-tab]')){b.classList.toggle('active',b.dataset.tab===activeTab);if(b.dataset.tab===activeTab)b.setAttribute('aria-current','page');else b.removeAttribute('aria-current');}
|
| 96 |
$('results-view').hidden=['methods','paper','about'].includes(activeTab);$('methods-view').hidden=activeTab!=='methods';$('paper-view').hidden=activeTab!=='paper';$('about-view').hidden=activeTab!=='about';$('chart-view').hidden=activeTab!=='tradeoffs';
|
| 97 |
+
$('score-controls').hidden=activeTab!=='overall';
|
| 98 |
+
for(const input of $('column-options').querySelectorAll('input'))input.parentElement.hidden=Boolean(cols.find(c=>c.key===input.dataset.column)?.overallOnly&&activeTab!=='overall');
|
| 99 |
+
if(activeTab!=='overall'&&cols.find(c=>c.key===sort.key)?.overallOnly)sort={key:'compression_rate',direction:1};
|
| 100 |
if(activeTab==='dataset'&&$('dataset').value==='all'){const first=[...datasets.keys()].find(k=>records.some(r=>r.dataset===k&&r.runtime===$('runtime').value));if(first)$('dataset').value=first;}
|
| 101 |
if(activeTab==='overall')$('dataset').value='all';
|
| 102 |
refresh();
|
|
|
|
| 104 |
function showMethod(method,focus=false) {
|
| 105 |
const row=currentRows.find(r=>r.method===method), detail=$('method-detail');
|
| 106 |
if(!row){selectedMethod=null;detail.hidden=true;return;}selectedMethod=method;
|
| 107 |
+
const meta=methods.get(method)??methods.get(row.source[0].configuration), pipeline=meta?.operator_pipeline, audit=snapshot.benchmark?.method_audits?.[row.family];
|
| 108 |
const source=row.source.slice().sort((a,b)=>a.dataset.localeCompare(b.dataset));
|
| 109 |
+
detail.innerHTML=`<div class="detail-head"><div><h2 id="detail-title">${escape(method)}</h2><p class="muted">${escape(row.family)} · ${escape(runtimeName(row.runtime))} · ${row.coverage} measured datasets</p></div><button id="close-detail" class="quiet-button">Close details</button></div><div class="detail-grid"><div><strong>${fmt(row.compression_rate,4)}</strong><span>average compression rate</span></div><div><strong>${fmt(row.compression_time,2)}</strong><span>encoding ns/point</span></div><div><strong>${fmt(row.decompression_time,2)}</strong><span>decoding ns/point</span></div><div><strong>${row.coverage} / ${row.totalDatasets}</strong><span>dataset coverage</span></div></div>${audit?`<div class="notice"><strong>Measurement audit · ${escape(audit.audited_at)}</strong>${[audit.implementation,audit.timing,audit.storage,audit.roundtrip,audit.comparison].map(n=>`<p>${escape(n)}</p>`).join('')}</div>`:''}${pipeline&&!audit?`<p class="source-code">Interpretive operator mapping: ${escape(typeof pipeline==='string'?pipeline:JSON.stringify(pipeline))}</p>`:''}<p class="muted">Dataset metrics below come from the published result files. Independent round-trip evidence and hardware are ${snapshot.benchmark?.roundtrip_evidence?'documented in the manifest':'not reported for this snapshot'}.</p><div class="detail-table"><table><thead><tr><th>Dataset</th><th>Configuration</th><th>Rate ↓</th><th>Encode ns/point ↓</th><th>Decode ns/point ↓</th><th>Original bytes</th><th>Compressed bytes</th><th>Dataset score ↓</th><th>Dataset Rank ↓</th><th>Rank candidates</th></tr></thead><tbody>${source.map(r=>`<tr><td>${escape(r.dataset)}</td><td>${escape(r.configuration)}</td><td class="numeric">${fmt(r.compression_rate,4)}</td><td class="numeric">${fmt(r.compression_time,2)}</td><td class="numeric">${fmt(r.decompression_time,2)}</td><td class="numeric">${fmt(r.original_bytes,0)}</td><td class="numeric">${fmt(r.compressed_bytes,0)}</td><td class="numeric">${fmt(r.dataset_score,4)}</td><td class="numeric">${fmt(r.dataset_rank,2)}</td><td class="numeric">${fmt(r.dataset_candidate_count,0)}</td></tr>`).join('')}</tbody></table></div>`;
|
| 110 |
detail.hidden=false;$('close-detail').addEventListener('click',()=>{detail.hidden=true;selectedMethod=null;});
|
| 111 |
if(focus){detail.scrollIntoView({block:'nearest',behavior:'auto'});detail.focus({preventScroll:true});}
|
| 112 |
}
|
|
|
|
| 128 |
}
|
| 129 |
function csv() {
|
| 130 |
const quote=v=>{let text=String(v??'');if(typeof v==='string'&&/^[=+@-]/.test(text))text="'"+text;return `"${text.replace(/"/g,'""')}"`;};
|
| 131 |
+
const weights=parseWeights($('weights').value);
|
| 132 |
+
const columns=['position','method','runtime','scope','row_mode','selected_dataset','sort_metric','sort_direction','average_compression_rate','overall_compression_rate','encode_ns_per_point','decode_ns_per_point','weight_a','weight_b','weight_c','normalized_encode','normalized_decode','composite_score','average_rank','rank_dataset_coverage','normalization_reference_rows','encode_min','encode_max','decode_min','decode_max','normalization','rank_definition','dataset_coverage','datasets_in_scope','selected_configuration_by_dataset'];
|
| 133 |
+
const body=currentRows.map((r,i)=>[i+1,r.method,r.runtime,scopeKey(),state().mode,state().dataset,sort.key,sort.direction===1?'ascending':'descending',r.compression_rate,r.overall_rate,r.compression_time,r.decompression_time,...weights.map(w=>w.toFixed(1)),r.normalized_compression_time,r.normalized_decompression_time,r.composite_score,r.average_rank,r.rank_coverage,r.normalization_reference_rows,r.encode_min,r.encode_max,r.decode_min,r.decode_max,'min-max over all measured rows in selected runtime/scope/row-mode before search and visibility filters','mean per-dataset composite-score fractional rank; per-dataset min-max times',r.coverage,r.totalDatasets,JSON.stringify(Object.fromEntries(r.source.map(s=>[s.dataset,s.configuration])))].map(quote).join(','));
|
| 134 |
const url=URL.createObjectURL(new Blob(['\ufeff'+[columns.join(','),...body].join('\r\n')],{type:'text/csv;charset=utf-8'}));const a=document.createElement('a');a.href=url;a.download=`THULab-compression-${state().runtime}-${scopeKey()}-${state().dataset}.csv`;a.click();setTimeout(()=>URL.revokeObjectURL(url),1000);
|
| 135 |
}
|
| 136 |
function renderAbout() {
|
| 137 |
const b=snapshot.benchmark||{},s=snapshot.source||{},c=snapshot.coverage||{};
|
| 138 |
const notes=[b.scope_note,b.aggregation_note,b.selection_note,...(b.notes||[]),...(b.measurement_boundaries||[])].filter(Boolean);
|
| 139 |
$('methodology').innerHTML=notes.map(n=>`<p class="notice">${escape(n)}</p>`).join('');
|
| 140 |
+
for(const [method,audit] of Object.entries(b.method_audits||{}))$('methodology').innerHTML+=`<div class="notice"><strong>${escape(method)} measurement audit · ${escape(audit.audited_at)}</strong>${[audit.implementation,audit.timing,audit.storage,audit.roundtrip,audit.comparison].map(n=>`<p>${escape(n)}</p>`).join('')}</div>`;
|
| 141 |
const definitions=b.metric_definitions||{};if(Object.keys(definitions).length)$('methodology').innerHTML+=`<dl class="definitions">${Object.entries(definitions).map(([k,v])=>`<div><dt>${escape(k.replaceAll('_',' '))}</dt><dd>${escape(typeof v==='string'?v:JSON.stringify(v))}</dd></div>`).join('')}</dl>`;
|
| 142 |
const exclusions=c.exclusions||c.excluded_datasets||snapshot.exclusions||[];
|
| 143 |
const info={ 'Snapshot exported':snapshot.exported_at||s.exported_at||'Not reported','Dataset scope generated':s.generated_at||'Not reported', 'Source checkout':s.git_commit||'Not reported','Source files':`${s.source_files?.length??0} metadata inputs and ${s.result_csv_files_used?.length??0} result CSVs (hashes in manifest)`,'Result entries across scopes':`${records.length} (scope slices can reuse a measurement)`,'Worktree source state':s.worktree_dirty_inputs?.length?'Includes current local changes; input file hashes identify the published snapshot.':'See manifest for source file hashes.','Runtime environment':b.hardware||'Hardware details are not recorded in this exported result snapshot.','Lossless evidence':b.roundtrip_evidence||'Lossless-design method filter from the source project; per-run round-trip verification is not recorded here.'};
|
|
|
|
| 158 |
$('dataset-count').textContent=countFmt(new Set(records.map(r=>r.dataset)).size);$('method-count').textContent=countFmt(new Set(records.map(r=>r.family)).size);$('runtime-count').textContent=runtimes.length;$('record-count').textContent=countFmt(snapshot.coverage?.runtime_configuration_count??new Set(records.map(r=>`${r.runtime}\0${r.method}`)).size);
|
| 159 |
const exported=snapshot.exported_at||snapshot.source?.exported_at||'not reported';$('snapshot').textContent=`Result snapshot exported · ${String(exported).replace('T',' ').slice(0,19)}${exported==='not reported'?'':' UTC'}`;$('footer-snapshot').textContent=`Source inputs documented in the manifest`;
|
| 160 |
$('column-options').innerHTML=cols.map(c=>`<label><input type="checkbox" data-column="${c.key}" ${c.visible?'checked':''}>${escape(c.label)}</label>`).join('');
|
| 161 |
+
$('weights').innerHTML=WEIGHT_TENTHS.map(w=>`<option value="${w.join(',')}" ${w.join(',')==='4,3,3'?'selected':''}>a = ${(w[0]/10).toFixed(1)} · b = ${(w[1]/10).toFixed(1)} · c = ${(w[2]/10).toFixed(1)}</option>`).join('');
|
| 162 |
+
$('weights').addEventListener('change',refresh);
|
| 163 |
for(const input of $('column-options').querySelectorAll('input'))input.addEventListener('change',()=>{cols.find(c=>c.key===input.dataset.column).visible=input.checked;renderTable();});
|
| 164 |
for(const id of ['runtime','dtype','precision','dataset','family','row-mode','complete-only'])$(id).addEventListener('change',refresh);$('search').addEventListener('input',refresh);$('chart-metric').addEventListener('change',renderChart);$('download').addEventListener('click',csv);
|
| 165 |
+
$('reset').addEventListener('click',()=>{$('runtime').value=runtimes.includes('cpp')?'cpp':runtimes[0];$('dtype').value='overall';$('precision').value='all';$('dataset').value='all';$('row-mode').value='family';$('search').value='';$('family').value='all';$('weights').value='4,3,3';$('complete-only').checked=false;selectedMethod=null;$('method-detail').hidden=true;sort={key:'compression_rate',direction:1};setTab(activeTab);});
|
| 166 |
for(const tab of document.querySelectorAll('[data-tab]'))tab.addEventListener('click',()=>{history.replaceState(null,'',`#${tab.dataset.tab}`);setTab(tab.dataset.tab);});
|
| 167 |
const renderPaper=()=>{const rows=(snapshot.paper_table3?.rows||[]).filter(r=>r.scope===$('paper-scope').value);$('paper-table').querySelector('tbody').innerHTML=rows.map(r=>`<tr><td>${escape(r.method)}</td><td class="numeric">${fmt(r.rank,1)}</td><td class="numeric">${fmt(r.average_compression_rate,4)}</td><td class="numeric">${fmt(r.average_compression_time_ns_per_point,1)}</td><td class="numeric">${fmt(r.average_decompression_time_ns_per_point,1)}</td><td class="numeric">${fmt(r.median_balanced_score,4)}</td></tr>`).join('');};$('paper-scope').addEventListener('change',renderPaper);renderPaper();
|
| 168 |
renderAbout();setTab(location.hash.slice(1)||'overall');
|
|
|
|
| 176 |
$('theme').addEventListener('click',()=>{const next=document.documentElement.dataset.theme==='dark'?'light':'dark';document.documentElement.dataset.theme=next;$('theme').textContent=next==='dark'?'Light mode':'Dark mode';try{localStorage.setItem('thulab-theme',next);}catch{}});
|
| 177 |
}
|
| 178 |
setupTheme();
|
| 179 |
+
try { const response=await fetch('./ui-data.json?v=20261008-rank-v1',{cache:'no-cache'});if(!response.ok)throw new Error(`Snapshot request returned ${response.status}`);snapshot=await response.json();init(); }
|
| 180 |
catch(e){$('load-error').hidden=false;$('load-error').textContent=`The result snapshot could not be loaded. Please reload the page or download the JSON from the repository. ${e.message}`;$('snapshot').textContent='Snapshot unavailable';$('leaderboard').querySelector('tbody').innerHTML='<tr><td class="empty-cell">Results unavailable.</td></tr>';}
|
data.json
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:47ddcf459b1eedd8b1dcf89d07ee544e10b527e6da24c64f81de18628b324454
|
| 3 |
+
size 28048937
|
export_results.py
CHANGED
|
@@ -462,12 +462,23 @@ def main() -> int:
|
|
| 462 |
"benchmark": {
|
| 463 |
"title": "THULab Time Series Compression Benchmark",
|
| 464 |
"implementation_runtimes": list(RUNTIMES),
|
| 465 |
-
"
|
| 466 |
-
"current_result_selection": "
|
| 467 |
"paper_result_selection": "the separate paper_table3 section contains its frozen 20-method C++ snapshot",
|
| 468 |
"scope_note": "Current result rows cover numeric int/float report evidence for the retained 19 public datasets. They reflect the columns present in each report; they do not assert full-column coverage beyond those files.",
|
| 469 |
"aggregation_note": "Average metrics are arithmetic means over available per-dataset measurements. Overall compression rate and weighted times use summed measured bytes, points, and nanoseconds only.",
|
| 470 |
-
"selection_note": "Configuration views retain every
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 471 |
"notes": [
|
| 472 |
"Missing combinations are unavailable rather than zero.",
|
| 473 |
"Hardware identity and round-trip verification are not reported by these source CSVs.",
|
|
|
|
| 462 |
"benchmark": {
|
| 463 |
"title": "THULab Time Series Compression Benchmark",
|
| 464 |
"implementation_runtimes": list(RUNTIMES),
|
| 465 |
+
"lossless_design_methods_only": True,
|
| 466 |
+
"current_result_selection": "visible configurations of methods categorized as lossless-design by the project; result-level float round trips are not established",
|
| 467 |
"paper_result_selection": "the separate paper_table3 section contains its frozen 20-method C++ snapshot",
|
| 468 |
"scope_note": "Current result rows cover numeric int/float report evidence for the retained 19 public datasets. They reflect the columns present in each report; they do not assert full-column coverage beyond those files.",
|
| 469 |
"aggregation_note": "Average metrics are arithmetic means over available per-dataset measurements. Overall compression rate and weighted times use summed measured bytes, points, and nanoseconds only.",
|
| 470 |
+
"selection_note": "Configuration views retain measured configurations passing the project's method-name filter. This does not certify every result as lossless. Family views choose the lowest-rate configuration independently for each dataset, scope, and runtime.",
|
| 471 |
+
"method_audits": {
|
| 472 |
+
"DBP": {
|
| 473 |
+
"audited_at": "2026-10-08",
|
| 474 |
+
"label": "Kernel measurements",
|
| 475 |
+
"implementation": "Project DBP encodes deltas with ZigZag and ULEB128; it is not Parquet miniblock bit-packing.",
|
| 476 |
+
"timing": "Float preparation/scaling is outside the reported codec timer.",
|
| 477 |
+
"storage": "Reported float payload sizes omit the scale/mode metadata needed for complete reconstruction.",
|
| 478 |
+
"roundtrip": "Integer payload round-trip checks do not establish end-to-end bit-exact float reconstruction. Current Java/Python catalog scaling paths lack a bit-exact guard; current C++ has an exact-scaling check and IEEE-bit fallback, but old reports do not identify the exact executable revision.",
|
| 479 |
+
"comparison": "Compared methods may use different columns or point counts. Check the per-dataset original byte counts before comparing results.",
|
| 480 |
+
}
|
| 481 |
+
},
|
| 482 |
"notes": [
|
| 483 |
"Missing combinations are unavailable rather than zero.",
|
| 484 |
"Hardware identity and round-trip verification are not reported by these source CSVs.",
|
index.html
CHANGED
|
@@ -6,8 +6,8 @@
|
|
| 6 |
<meta name="description" content="THULab time-series compression benchmark: measured compression rates, encoding and decoding costs, and dataset coverage.">
|
| 7 |
<meta name="color-scheme" content="light dark">
|
| 8 |
<title>THULab | Time Series Compression Benchmark</title>
|
| 9 |
-
<link rel="stylesheet" href="style.css">
|
| 10 |
-
<script src="app.js" type="module"></script>
|
| 11 |
</head>
|
| 12 |
<body>
|
| 13 |
<a class="skip-link" href="#content">Skip to results</a>
|
|
@@ -52,6 +52,11 @@
|
|
| 52 |
<div class="toolbar-actions"><button id="reset" class="quiet-button">Reset filters</button><button id="download" class="primary-button" disabled>Download CSV</button></div>
|
| 53 |
</div>
|
| 54 |
<div class="results-note" id="results-note">Compression rate = compressed bytes / original bytes. Lower rates and lower ns/point are better.</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
<div class="table-settings">
|
| 56 |
<label><input id="complete-only" type="checkbox"> Only methods covering the full selection</label>
|
| 57 |
<details><summary>Display columns</summary><div id="column-options" class="column-options"></div></details>
|
|
@@ -84,6 +89,8 @@
|
|
| 84 |
<div><dt>Overall compression rate ↓</dt><dd>Total compressed bytes divided by total original bytes over the selected measured records.</dd></div>
|
| 85 |
<div><dt>Encoding / decoding cost ↓</dt><dd>Nanoseconds per data point. The aggregation boundary is documented with the snapshot below.</dd></div>
|
| 86 |
<div><dt>Coverage</dt><dd>Datasets with usable measurements for a method out of the datasets in the selected scope. Missing results are never filled with zero.</dd></div>
|
|
|
|
|
|
|
| 87 |
</dl>
|
| 88 |
<h3>Reading the rankings</h3>
|
| 89 |
<p>Methods with different coverage are measured on different inputs. Coverage counts datasets, not identical columns or point counts. Use the full-coverage filter and inspect a single dataset's input sizes before drawing a direct comparison. Runtime filters keep Python, C++, and Java measurements separate.</p>
|
|
|
|
| 6 |
<meta name="description" content="THULab time-series compression benchmark: measured compression rates, encoding and decoding costs, and dataset coverage.">
|
| 7 |
<meta name="color-scheme" content="light dark">
|
| 8 |
<title>THULab | Time Series Compression Benchmark</title>
|
| 9 |
+
<link rel="stylesheet" href="style.css?v=20261008-rank-v1">
|
| 10 |
+
<script src="app.js?v=20261008-rank-v1" type="module"></script>
|
| 11 |
</head>
|
| 12 |
<body>
|
| 13 |
<a class="skip-link" href="#content">Skip to results</a>
|
|
|
|
| 52 |
<div class="toolbar-actions"><button id="reset" class="quiet-button">Reset filters</button><button id="download" class="primary-button" disabled>Download CSV</button></div>
|
| 53 |
</div>
|
| 54 |
<div class="results-note" id="results-note">Compression rate = compressed bytes / original bytes. Lower rates and lower ns/point are better.</div>
|
| 55 |
+
<p id="audit-notice" class="audit-notice" hidden></p>
|
| 56 |
+
<div id="score-controls" class="score-controls">
|
| 57 |
+
<label>Score weights · a: rate / b: encode / c: decode<select id="weights" aria-label="Score weights a b c"></select></label>
|
| 58 |
+
<p id="score-note" class="muted" aria-live="polite">Positive weights in steps of 0.1, summing to 1.0.</p>
|
| 59 |
+
</div>
|
| 60 |
<div class="table-settings">
|
| 61 |
<label><input id="complete-only" type="checkbox"> Only methods covering the full selection</label>
|
| 62 |
<details><summary>Display columns</summary><div id="column-options" class="column-options"></div></details>
|
|
|
|
| 89 |
<div><dt>Overall compression rate ↓</dt><dd>Total compressed bytes divided by total original bytes over the selected measured records.</dd></div>
|
| 90 |
<div><dt>Encoding / decoding cost ↓</dt><dd>Nanoseconds per data point. The aggregation boundary is documented with the snapshot below.</dd></div>
|
| 91 |
<div><dt>Coverage</dt><dd>Datasets with usable measurements for a method out of the datasets in the selected scope. Missing results are never filled with zero.</dd></div>
|
| 92 |
+
<div><dt>Composite score ↓</dt><dd>a × average compression rate + b × normalized average encoding time + c × normalized average decoding time. Each time uses min–max over all measured rows in the selected runtime, scope and row mode, before search, family and coverage filters. If all times are equal, their normalized value is zero. All measured inputs must have usable timing metrics.</dd></div>
|
| 93 |
+
<div><dt>Average Rank ↓</dt><dd>Compute the same weighted score within each dataset, normalizing times within that dataset. Rank lower scores first; ties share their average position. Then take the arithmetic mean of the method's available dataset ranks. Weights are strictly positive tenths summing to 1.0. Missing measurements do not receive ranks.</dd></div>
|
| 94 |
</dl>
|
| 95 |
<h3>Reading the rankings</h3>
|
| 96 |
<p>Methods with different coverage are measured on different inputs. Coverage counts datasets, not identical columns or point counts. Use the full-coverage filter and inspect a single dataset's input sizes before drawing a direct comparison. Runtime filters keep Python, C++, and Java measurements separate.</p>
|
manifest.json
CHANGED
|
@@ -23,7 +23,7 @@
|
|
| 23 |
"source_report_file_count": 114
|
| 24 |
},
|
| 25 |
"data_file": "data.json",
|
| 26 |
-
"data_sha256": "
|
| 27 |
"excluded_content": [
|
| 28 |
"raw datasets",
|
| 29 |
"user uploads",
|
|
|
|
| 23 |
"source_report_file_count": 114
|
| 24 |
},
|
| 25 |
"data_file": "data.json",
|
| 26 |
+
"data_sha256": "47ddcf459b1eedd8b1dcf89d07ee544e10b527e6da24c64f81de18628b324454",
|
| 27 |
"excluded_content": [
|
| 28 |
"raw datasets",
|
| 29 |
"user uploads",
|
ranking.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Ranking references include all measured methods in the selected runtime,
|
| 2 |
+
// scope and row mode. Search, family and coverage filters only hide rows.
|
| 3 |
+
export const WEIGHT_TENTHS = [];
|
| 4 |
+
for (let a=1;a<=8;a++) for (let b=1;b<=9-a;b++) WEIGHT_TENTHS.push([a,b,10-a-b]);
|
| 5 |
+
|
| 6 |
+
export function parseWeights(value) {
|
| 7 |
+
const parts=String(value).split(',').map(Number);
|
| 8 |
+
if(parts.length!==3||parts.some(n=>!Number.isInteger(n)||n<=0)||parts.reduce((s,n)=>s+n,0)!==10) throw new Error('Weights must be positive tenths summing to 1.0.');
|
| 9 |
+
return parts.map(n=>n/10);
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
const finite=v=>v!==null&&v!==undefined&&Number.isFinite(v);
|
| 13 |
+
const valid=r=>finite(r.compression_rate)&&r.compression_rate>0&&finite(r.compression_time)&&r.compression_time>=0&&finite(r.decompression_time)&&r.decompression_time>=0;
|
| 14 |
+
const average=values=>values.length?values.reduce((s,v)=>s+v,0)/values.length:null;
|
| 15 |
+
|
| 16 |
+
function bounds(rows,key) {
|
| 17 |
+
const values=rows.map(r=>r[key]).filter(finite);
|
| 18 |
+
return values.length?[Math.min(...values),Math.max(...values)]:null;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
export function minMax(value,range) {
|
| 22 |
+
if(!finite(value)||!range)return null;
|
| 23 |
+
return range[0]===range[1]?0:(value-range[0])/(range[1]-range[0]);
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
function score(row,weights,encodeRange,decodeRange) {
|
| 27 |
+
const encode=minMax(row.compression_time,encodeRange),decode=minMax(row.decompression_time,decodeRange);
|
| 28 |
+
return {normalized_compression_time:encode,normalized_decompression_time:decode,composite_score:valid(row)&&encode!==null&&decode!==null?weights[0]*row.compression_rate+weights[1]*encode+weights[2]*decode:null};
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
export function addRankingMetrics(rows,weights) {
|
| 32 |
+
if(weights.length!==3||weights.some(w=>!finite(w)||w<=0)||Math.abs(weights.reduce((s,w)=>s+w,0)-1)>1e-12)throw new Error('Invalid scoring weights.');
|
| 33 |
+
const candidates=rows.filter(r=>valid(r)&&r.source.every(valid));
|
| 34 |
+
const encodeRange=bounds(candidates,'compression_time'),decodeRange=bounds(candidates,'decompression_time');
|
| 35 |
+
const byDataset=new Map(),ranks=new Map(),details=new Map();
|
| 36 |
+
for(const row of rows) {
|
| 37 |
+
ranks.set(row.method,[]);
|
| 38 |
+
for(const record of row.source.filter(valid)) {
|
| 39 |
+
if(!byDataset.has(record.dataset))byDataset.set(record.dataset,[]);
|
| 40 |
+
byDataset.get(record.dataset).push({...record,method:row.method});
|
| 41 |
+
}
|
| 42 |
+
}
|
| 43 |
+
for(const records of byDataset.values()) {
|
| 44 |
+
const encode=bounds(records,'compression_time'),decode=bounds(records,'decompression_time');
|
| 45 |
+
const ordered=records.map(r=>({...r,...score(r,weights,encode,decode)})).sort((a,b)=>a.composite_score-b.composite_score||a.method.localeCompare(b.method));
|
| 46 |
+
for(let start=0;start<ordered.length;) {
|
| 47 |
+
let end=start+1;
|
| 48 |
+
while(end<ordered.length&&ordered[end].composite_score===ordered[start].composite_score)end++;
|
| 49 |
+
const rank=(start+1+end)/2;
|
| 50 |
+
for(let i=start;i<end;i++) {
|
| 51 |
+
ranks.get(ordered[i].method).push(rank);
|
| 52 |
+
details.set(`${ordered[i].method}\0${ordered[i].dataset}`,{dataset_score:ordered[i].composite_score,dataset_rank:rank,dataset_candidate_count:ordered.length});
|
| 53 |
+
}
|
| 54 |
+
start=end;
|
| 55 |
+
}
|
| 56 |
+
}
|
| 57 |
+
return rows.map(row=>({...row,...(candidates.includes(row)?score(row,weights,encodeRange,decodeRange):{normalized_compression_time:null,normalized_decompression_time:null,composite_score:null}),average_rank:average(ranks.get(row.method)),rank_coverage:ranks.get(row.method).length,normalization_reference_rows:candidates.length,encode_min:encodeRange?.[0]??null,encode_max:encodeRange?.[1]??null,decode_min:decodeRange?.[0]??null,decode_max:decodeRange?.[1]??null,source:row.source.map(r=>({...r,...details.get(`${row.method}\0${r.dataset}`)}))}));
|
| 58 |
+
}
|
style.css
CHANGED
|
@@ -7,3 +7,5 @@
|
|
| 7 |
@media(max-width:768px){main{padding:0 16px}.site-header{padding:12px 16px;min-height:64px}.header-links{gap:10px;font-size:12px}.brand{gap:8px;font-size:13px}.brand-mark{width:34px;height:34px}.intro{padding:28px 0 20px}.intro-text{font-size:14px}.filters{grid-template-columns:repeat(2,minmax(0,1fr));padding:14px;gap:12px}.summary-strip{padding:14px 0}.summary-strip strong{font-size:21px}.summary-strip span{display:block;font-size:10px;line-height:1.4;padding:5px}.toolbar{align-items:start;flex-direction:column}.toolbar-actions{width:100%;justify-content:space-between}.table-settings{align-items:start;font-size:11px}.table-settings label{max-width:65%}.table-footer{flex-direction:column;gap:4px}.tab{padding:10px 14px;font-size:13px}.definitions,.method-groups{grid-template-columns:1fr}.detail-grid{grid-template-columns:repeat(2,1fr)}.method-detail{padding:16px}.chart-controls{flex-direction:column;align-items:start}.chart-controls label{width:100%}.chart{padding:6px}.summary-strip>div{min-width:0}footer{flex-direction:column;gap:4px}.table-wrap{max-height:550px}.header-links>a{display:none}h2{font-size:19px}}
|
| 8 |
@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;transition:none!important}}
|
| 9 |
.intro{padding:16px 0 10px}.eyebrow{display:none}h1{font-size:clamp(24px,2.8vw,30px);margin-bottom:8px}.intro-text{font-size:13px}.snapshot{font-size:11px;margin-top:6px}.summary-strip{padding:10px 0}.summary-strip strong{font-size:20px}.filters{padding:12px;gap:8px}.toolbar{margin-top:12px;margin-bottom:4px}
|
|
|
|
|
|
|
|
|
| 7 |
@media(max-width:768px){main{padding:0 16px}.site-header{padding:12px 16px;min-height:64px}.header-links{gap:10px;font-size:12px}.brand{gap:8px;font-size:13px}.brand-mark{width:34px;height:34px}.intro{padding:28px 0 20px}.intro-text{font-size:14px}.filters{grid-template-columns:repeat(2,minmax(0,1fr));padding:14px;gap:12px}.summary-strip{padding:14px 0}.summary-strip strong{font-size:21px}.summary-strip span{display:block;font-size:10px;line-height:1.4;padding:5px}.toolbar{align-items:start;flex-direction:column}.toolbar-actions{width:100%;justify-content:space-between}.table-settings{align-items:start;font-size:11px}.table-settings label{max-width:65%}.table-footer{flex-direction:column;gap:4px}.tab{padding:10px 14px;font-size:13px}.definitions,.method-groups{grid-template-columns:1fr}.detail-grid{grid-template-columns:repeat(2,1fr)}.method-detail{padding:16px}.chart-controls{flex-direction:column;align-items:start}.chart-controls label{width:100%}.chart{padding:6px}.summary-strip>div{min-width:0}footer{flex-direction:column;gap:4px}.table-wrap{max-height:550px}.header-links>a{display:none}h2{font-size:19px}}
|
| 8 |
@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;transition:none!important}}
|
| 9 |
.intro{padding:16px 0 10px}.eyebrow{display:none}h1{font-size:clamp(24px,2.8vw,30px);margin-bottom:8px}.intro-text{font-size:13px}.snapshot{font-size:11px;margin-top:6px}.summary-strip{padding:10px 0}.summary-strip strong{font-size:20px}.filters{padding:12px;gap:8px}.toolbar{margin-top:12px;margin-bottom:4px}
|
| 10 |
+
.score-controls{display:flex;align-items:start;gap:20px;padding:12px 14px;margin:6px 0 12px;border:1px solid var(--line);border-radius:var(--radius);background:var(--soft)}.score-controls label{min-width:320px}.score-controls p{font-size:11px;line-height:1.7;margin:0;max-width:90ch}.score-controls select{height:35px}@media(max-width:768px){.score-controls{flex-direction:column;gap:10px}.score-controls label{min-width:0;width:100%}}
|
| 11 |
+
.audit-badge{display:block;color:var(--muted);font-size:10px;margin-top:4px}.audit-notice{font-size:12px;padding:10px 14px;margin:8px 0;border-left:3px solid var(--accent);background:var(--accent-soft);color:var(--text)}
|
ui-data.json
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|