Spaces:
Running
Running
Average compression times over measured columns
Browse filesCompute Compress and Decompress ns/point independently for each measured column, then use an equal-column arithmetic mean across the selected datasets. Preserve compression rates, sizes, configurations, and dataset coverage. Keep aggregate-only historical timings unavailable. Update leaderboard scores, ranks, metric labels, charts, details, and CSV exports.
- README.md +6 -4
- app.js +21 -16
- apply_dataset_scope.py +4 -3
- build_ui_payload.py +1 -1
- data.json +2 -2
- export_results.py +94 -33
- index.html +9 -9
- manifest.json +17 -3
- ui-data.json +0 -0
README.md
CHANGED
|
@@ -6,7 +6,7 @@ colorTo: gray
|
|
| 6 |
sdk: static
|
| 7 |
app_file: index.html
|
| 8 |
pinned: false
|
| 9 |
-
short_description: Compression rates and
|
| 10 |
---
|
| 11 |
|
| 12 |
# THULab Time Series Compression Benchmark
|
|
@@ -24,6 +24,8 @@ python3 deploy/huggingface-compressionbenchmark/export_results.py
|
|
| 24 |
python3 deploy/huggingface-compressionbenchmark/apply_dataset_scope.py
|
| 25 |
# Reapply verified measurement updates before packing the browser payload.
|
| 26 |
python3 scripts/integrate_dbp_leaderboard.py --measurements /path/to/frozen-measurements.json --expected-sha256 VERIFIED_SHA256 --receipt /path/to/integration-receipt.json
|
|
|
|
|
|
|
| 27 |
python3 deploy/huggingface-compressionbenchmark/build_ui_payload.py
|
| 28 |
```
|
| 29 |
|
|
@@ -35,7 +37,7 @@ Only measurements in the authoritative current project scope are exported. Missi
|
|
| 35 |
|
| 36 |
- Overall and per-dataset leaderboards with runtime, data-type, precision, family, and method filters
|
| 37 |
- Sortable metrics, optional columns, and CSV download of the selected ranking
|
| 38 |
-
- Compression rate versus
|
| 39 |
- Dataset-level method details, snapshot provenance, and metric definitions
|
| 40 |
- Searchable 169-dataset catalog grouped into six source categories, with explicit measurement availability and source links
|
| 41 |
- Overall composite score and average dataset Rank, with 66 valid non-negative one-decimal weight combinations, including zero weights
|
|
@@ -43,8 +45,8 @@ Only measurements in the authoritative current project scope are exported. Missi
|
|
| 43 |
|
| 44 |
The summary dataset count and Dataset coverage denominator use the same measured dataset set for the selected runtime, data type, and dataset selection; the default Overall view has 164 measured datasets. All 169 catalog entries remain searchable and selectable in the dataset browser. Dataset coverage can differ by method and runtime; unavailable combinations show **No published measurements**, remain excluded from rankings, and are never converted to zero-valued results. The default family view selects the best measured compression-rate configuration independently for each measured dataset. Switch to **Exact configuration** to hold a configuration fixed. Runtime results stay separate.
|
| 45 |
|
| 46 |
-
The Overall score is `a × average rate + b × normalized average
|
| 47 |
|
| 48 |
-
|
| 49 |
|
| 50 |
Source project: https://github.com/xjz17/web_compression
|
|
|
|
| 6 |
sdk: static
|
| 7 |
app_file: index.html
|
| 8 |
pinned: false
|
| 9 |
+
short_description: Compression rates and compress/decompress costs
|
| 10 |
---
|
| 11 |
|
| 12 |
# THULab Time Series Compression Benchmark
|
|
|
|
| 24 |
python3 deploy/huggingface-compressionbenchmark/apply_dataset_scope.py
|
| 25 |
# Reapply verified measurement updates before packing the browser payload.
|
| 26 |
python3 scripts/integrate_dbp_leaderboard.py --measurements /path/to/frozen-measurements.json --expected-sha256 VERIFIED_SHA256 --receipt /path/to/integration-receipt.json
|
| 27 |
+
# Verify the published source identities and rebuild equal-column timing means.
|
| 28 |
+
python3 scripts/update_site_column_means.py --receipt /path/to/column-mean-receipt.json
|
| 29 |
python3 deploy/huggingface-compressionbenchmark/build_ui_payload.py
|
| 30 |
```
|
| 31 |
|
|
|
|
| 37 |
|
| 38 |
- Overall and per-dataset leaderboards with runtime, data-type, precision, family, and method filters
|
| 39 |
- Sortable metrics, optional columns, and CSV download of the selected ranking
|
| 40 |
+
- Compression rate versus compression/decompression cost plots
|
| 41 |
- Dataset-level method details, snapshot provenance, and metric definitions
|
| 42 |
- Searchable 169-dataset catalog grouped into six source categories, with explicit measurement availability and source links
|
| 43 |
- Overall composite score and average dataset Rank, with 66 valid non-negative one-decimal weight combinations, including zero weights
|
|
|
|
| 45 |
|
| 46 |
The summary dataset count and Dataset coverage denominator use the same measured dataset set for the selected runtime, data type, and dataset selection; the default Overall view has 164 measured datasets. All 169 catalog entries remain searchable and selectable in the dataset browser. Dataset coverage can differ by method and runtime; unavailable combinations show **No published measurements**, remain excluded from rankings, and are never converted to zero-valued results. The default family view selects the best measured compression-rate configuration independently for each measured dataset. Switch to **Exact configuration** to hold a configuration fixed. Runtime results stay separate.
|
| 47 |
|
| 48 |
+
The Overall score is `a × average rate + b × normalized average compression time + c × normalized average decompression 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`.
|
| 49 |
|
| 50 |
+
Compression and decompression costs are calculated independently for each usable numeric column as ns/point, then reported as the arithmetic mean across those columns; each column contributes equally. Overall timing pools all measured columns across the selected datasets. If any selected dataset/configuration lacks column-level timing evidence, that Overall timing is unavailable. Historical aggregate-only timing rows without recoverable per-column evidence remain unavailable and are never treated as one synthetic column. Native-float measurements use valid native values; historical imported rows retain their source 8-byte-equivalent point definition. Timing comparisons should therefore stay within the same runtime and measurement contract.
|
| 51 |
|
| 52 |
Source project: https://github.com/xjz17/web_compression
|
app.js
CHANGED
|
@@ -3,6 +3,10 @@ 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;
|
| 5 |
const mean = (values) => { const a = values.map(number).filter((n) => n !== null); return a.length ? a.reduce((s,n) => s+n,0)/a.length : null; };
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
const fmt = (v, digits=3) => number(v) === null ? 'Not reported' : Number(v).toLocaleString('en-US',{maximumFractionDigits:digits,minimumFractionDigits:digits});
|
| 7 |
const countFmt = (v) => Number(v).toLocaleString('en-US');
|
| 8 |
const runtimeName = (v) => ({cpp:'C++',java:'Java',python:'Python'}[v] || v);
|
|
@@ -12,10 +16,11 @@ const cols = [
|
|
| 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:'
|
| 16 |
-
{key:'decompression_time',label:'
|
| 17 |
-
{key:'normalized_compression_time',label:'Normalized
|
| 18 |
-
{key:'normalized_decompression_time',label:'Normalized
|
|
|
|
| 19 |
{key:'compressed_bytes',label:'Compressed bytes',digits:0,visible:false},
|
| 20 |
{key:'original_bytes',label:'Original bytes',digits:0,visible:false},
|
| 21 |
];
|
|
@@ -50,7 +55,7 @@ function aggregate(input) {
|
|
| 50 |
for(const [method,source] of groups) {
|
| 51 |
const hasBytes=source.every(r=>r.original_bytes!==null && r.original_bytes>0 && r.compressed_bytes!==null && r.compressed_bytes>0);
|
| 52 |
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;
|
| 53 |
-
rows.push({method,family:source[0].family,runtime:source[0].runtime,compression_rate:mean(source.map(r=>r.compression_rate)),compression_time:
|
| 54 |
}
|
| 55 |
const s=state();
|
| 56 |
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)=>{
|
|
@@ -64,7 +69,7 @@ function renderTable() {
|
|
| 64 |
const visible=cols.filter(c=>c.visible&&(!c.overallOnly||activeTab==='overall'));
|
| 65 |
$('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>`;
|
| 66 |
const minima=Object.fromEntries(visible.map(c=>[c.key,Math.min(...currentRows.map(r=>number(r[c.key])).filter(n=>n!==null))]));
|
| 67 |
-
$('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 ${!['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>`;
|
| 68 |
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();});
|
| 69 |
for(const button of $('leaderboard').querySelectorAll('[data-method]'))button.addEventListener('click',()=>showMethod(button.dataset.method,true));
|
| 70 |
$('row-count').textContent=`${currentRows.length} ${state().mode==='family'?'method families':'method configurations'} · sorted by ${cols.find(c=>c.key===sort.key)?.label??sort.key}`;
|
|
@@ -73,7 +78,7 @@ function renderTable() {
|
|
| 73 |
function refresh() {
|
| 74 |
if(!snapshot)return;
|
| 75 |
$('precision').disabled=$('dtype').value!=='float';
|
| 76 |
-
const selectedDataset=state().dataset,selectedHasMeasurements=selectedDataset==='all'||records.some(r=>r.dataset===selectedDataset),columnCoverageNote='
|
| 77 |
if(!selectedHasMeasurements) {
|
| 78 |
$('results-note').textContent=`No published measurements are available for this catalog dataset. It is excluded from rankings rather than treated as zero. ${columnCoverageNote}`;
|
| 79 |
} else if(scopeKey()==='float'&&!records.some(r=>r.scope==='float')) {
|
|
@@ -83,7 +88,7 @@ function refresh() {
|
|
| 83 |
} 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.'} ${columnCoverageNote}`;
|
| 84 |
currentRows=aggregate(filteredRecords(true));
|
| 85 |
const weights=parseWeights($('weights').value);
|
| 86 |
-
$('score-note').textContent=`Score = ${weights[0].toFixed(1)} × average rate + ${weights[1].toFixed(1)} × normalized
|
| 87 |
const s=state(),scope=scopeKey(),measuredCount=coverageDatasetCount(),ds=s.dataset==='all'?`all ${measuredCount} measured datasets`:datasetDisplayName(s.dataset);
|
| 88 |
$('dataset-count').textContent=countFmt(measuredCount);$('dataset-count-label').textContent='measured datasets';
|
| 89 |
const names={overall:'available numeric measurements',integer:'integer columns',float:'floating-point columns',fixed_float:'fixed-precision float columns',nonfixed_float:'non-fixed-precision float columns'};
|
|
@@ -110,7 +115,7 @@ function showMethod(method,focus=false) {
|
|
| 110 |
if(!row){selectedMethod=null;detail.hidden=true;return;}selectedMethod=method;
|
| 111 |
const meta=methods.get(method)??methods.get(row.source[0].configuration), pipeline=meta?.operator_pipeline;
|
| 112 |
const source=row.source.slice().sort((a,b)=>a.dataset.localeCompare(b.dataset));
|
| 113 |
-
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>
|
| 114 |
detail.hidden=false;$('close-detail').addEventListener('click',()=>{detail.hidden=true;selectedMethod=null;});
|
| 115 |
if(focus){detail.scrollIntoView({block:'nearest',behavior:'auto'});detail.focus({preventScroll:true});}
|
| 116 |
}
|
|
@@ -125,16 +130,16 @@ function renderChart() {
|
|
| 125 |
let graph='';
|
| 126 |
for(let i=0;i<=5;i++){const rate=maxRate*i/5,px=x(rate);graph+=`<line class="gridline" x1="${px}" y1="${pad.top}" x2="${px}" y2="${height-pad.bottom}"/><text class="axis-label" x="${px}" y="${height-pad.bottom+25}" text-anchor="middle">${fmt(rate,2)}</text>`;}
|
| 127 |
for(let i=minLog;i<=maxLog;i++){const n=10**i,py=y(n);graph+=`<line class="gridline" x1="${pad.left}" y1="${py}" x2="${width-pad.right}" y2="${py}"/><text class="axis-label" x="${pad.left-12}" y="${py+4}" text-anchor="end">${n.toLocaleString('en-US',{maximumFractionDigits:3})}</text>`;}
|
| 128 |
-
for(const r of valid)graph+=`<circle class="point" tabindex="0" role="button" aria-label="${escape(r.method)}: compression rate ${fmt(r.compression_rate,4)}, ${metric==='compression_time'?'
|
| 129 |
-
graph+=`<text class="axis-label" x="${width/2}" y="${height-13}" text-anchor="middle">Average compression rate (compressed / original) ↓</text><text class="axis-label" transform="translate(20 ${height/2}) rotate(-90)" text-anchor="middle">${metric==='compression_time'?'
|
| 130 |
-
$('chart').innerHTML=`<svg viewBox="0 0 ${width} ${height}" role="img" aria-label="Measured compression rates versus ${metric==='compression_time'?'
|
| 131 |
for(const p of $('chart').querySelectorAll('[data-method]')){p.addEventListener('click',()=>showMethod(p.dataset.method,true));p.addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();showMethod(p.dataset.method,true);}});}
|
| 132 |
}
|
| 133 |
function csv() {
|
| 134 |
const quote=v=>{let text=String(v??'');if(typeof v==='string'&&/^[=+@-]/.test(text))text="'"+text;return `"${text.replace(/"/g,'""')}"`;};
|
| 135 |
const weights=parseWeights($('weights').value);
|
| 136 |
-
const columns=['position','method','runtime','scope','row_mode','selected_dataset','sort_metric','sort_direction','average_compression_rate','overall_compression_rate','
|
| 137 |
-
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(','));
|
| 138 |
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);
|
| 139 |
}
|
| 140 |
function safeHttpUrl(value) {
|
|
@@ -276,7 +281,7 @@ function renderAbout() {
|
|
| 276 |
$('provenance').innerHTML=`<table class="provenance-table"><tbody>${Object.entries(info).map(([k,v])=>`<tr><th>${escape(k)}</th><td class="source-code">${escape(v)}</td></tr>`).join('')}</tbody></table>${exclusions.length?`<h3>Excluded or unavailable results</h3><p class="source-code">${escape(typeof exclusions==='string'?exclusions:JSON.stringify(exclusions))}</p>`:''}`;
|
| 277 |
}
|
| 278 |
function init() {
|
| 279 |
-
if(snapshot.compact_records){const t=snapshot.record_tables;snapshot.dataset_results=snapshot.compact_records.map(r=>({dataset:t.datasets[r[0]],scope:t.scopes[r[1]],runtime:t.runtimes[r[2]],family:t.families[r[3]],method:t.configurations[r[4]],algorithm_variant:t.configurations[r[4]],compression_rate:r[5],compression_time_ns_per_point:r[6],decompression_time_ns_per_point:r[7],original_size_bytes:r[8],compressed_size_bytes:r[9]}));delete snapshot.compact_records;}
|
| 280 |
for(const m of snapshot.methods||[]) { const method=typeof m==='string'?{name:m}:m;methods.set(method.name??method.method,method);for(const c of method.configurations||[])methods.set(c.algorithm_variant,{...method,name:c.algorithm_variant,family:method.name}); }
|
| 281 |
for(const d of snapshot.datasets||[])datasets.set(d.key??d.dataset,d);
|
| 282 |
records=(snapshot.dataset_results||snapshot.records||[]).map(normalizeRecord).filter(r=>r.method&&r.dataset&&r.compression_rate>0);
|
|
@@ -308,5 +313,5 @@ function setupTheme() {
|
|
| 308 |
setupTheme();
|
| 309 |
void loadReferenceCatalog();
|
| 310 |
void loadDatasetCatalog();
|
| 311 |
-
try { const response=await fetch('./ui-data.json?v=20261009-
|
| 312 |
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>';}
|
|
|
|
| 3 |
const escape = (value) => String(value ?? '').replace(/[&<>"']/g, (c) => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
| 4 |
const number = (v) => v != null && Number.isFinite(Number(v)) ? Number(v) : null;
|
| 5 |
const mean = (values) => { const a = values.map(number).filter((n) => n !== null); return a.length ? a.reduce((s,n) => s+n,0)/a.length : null; };
|
| 6 |
+
function columnMean(input,key) {
|
| 7 |
+
if(!input.length||input.some(r=>number(r[key])===null||!Number.isInteger(r.timing_column_count)||r.timing_column_count<=0))return null;
|
| 8 |
+
return input.reduce((sum,r)=>sum+r[key]*r.timing_column_count,0)/input.reduce((sum,r)=>sum+r.timing_column_count,0);
|
| 9 |
+
}
|
| 10 |
const fmt = (v, digits=3) => number(v) === null ? 'Not reported' : Number(v).toLocaleString('en-US',{maximumFractionDigits:digits,minimumFractionDigits:digits});
|
| 11 |
const countFmt = (v) => Number(v).toLocaleString('en-US');
|
| 12 |
const runtimeName = (v) => ({cpp:'C++',java:'Java',python:'Python'}[v] || v);
|
|
|
|
| 16 |
{key:'composite_score',label:'Composite score ↓',digits:4,visible:true,overallOnly:true},
|
| 17 |
{key:'compression_rate',label:'Average rate ↓',digits:4,visible:true},
|
| 18 |
{key:'overall_rate',label:'Overall rate ↓',digits:4,visible:false},
|
| 19 |
+
{key:'compression_time',label:'Compress ns/point ↓',digits:2,visible:true},
|
| 20 |
+
{key:'decompression_time',label:'Decompress ns/point ↓',digits:2,visible:true},
|
| 21 |
+
{key:'normalized_compression_time',label:'Normalized compress ↓',digits:4,visible:false,overallOnly:true},
|
| 22 |
+
{key:'normalized_decompression_time',label:'Normalized decompress ↓',digits:4,visible:false,overallOnly:true},
|
| 23 |
+
{key:'timing_column_count',label:'Measured columns',digits:0,visible:false},
|
| 24 |
{key:'compressed_bytes',label:'Compressed bytes',digits:0,visible:false},
|
| 25 |
{key:'original_bytes',label:'Original bytes',digits:0,visible:false},
|
| 26 |
];
|
|
|
|
| 55 |
for(const [method,source] of groups) {
|
| 56 |
const hasBytes=source.every(r=>r.original_bytes!==null && r.original_bytes>0 && r.compressed_bytes!==null && r.compressed_bytes>0);
|
| 57 |
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;
|
| 58 |
+
rows.push({method,family:source[0].family,runtime:source[0].runtime,compression_rate:mean(source.map(r=>r.compression_rate)),compression_time:columnMean(source,'compression_time'),decompression_time:columnMean(source,'decompression_time'),timing_column_count:source.reduce((sum,r)=>sum+r.timing_column_count,0),overall_rate:hasBytes?compressed/original:null,original_bytes:original,compressed_bytes:compressed,coverage:new Set(source.map(r=>r.dataset)).size,totalDatasets,source});
|
| 59 |
}
|
| 60 |
const s=state();
|
| 61 |
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)=>{
|
|
|
|
| 69 |
const visible=cols.filter(c=>c.visible&&(!c.overallOnly||activeTab==='overall'));
|
| 70 |
$('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>`;
|
| 71 |
const minima=Object.fromEntries(visible.map(c=>[c.key,Math.min(...currentRows.map(r=>number(r[c.key])).filter(n=>n!==null))]));
|
| 72 |
+
$('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 ${!['compressed_bytes','original_bytes','rank_coverage','timing_column_count'].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>`;
|
| 73 |
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();});
|
| 74 |
for(const button of $('leaderboard').querySelectorAll('[data-method]'))button.addEventListener('click',()=>showMethod(button.dataset.method,true));
|
| 75 |
$('row-count').textContent=`${currentRows.length} ${state().mode==='family'?'method families':'method configurations'} · sorted by ${cols.find(c=>c.key===sort.key)?.label??sort.key}`;
|
|
|
|
| 78 |
function refresh() {
|
| 79 |
if(!snapshot)return;
|
| 80 |
$('precision').disabled=$('dtype').value!=='float';
|
| 81 |
+
const selectedDataset=state().dataset,selectedHasMeasurements=selectedDataset==='all'||records.some(r=>r.dataset===selectedDataset),columnCoverageNote='Compress and Decompress times are arithmetic means of each measured column’s ns/point; every column has equal weight. Column coverage may differ by method. Historical aggregate-only timings are not reported as column means.';
|
| 82 |
if(!selectedHasMeasurements) {
|
| 83 |
$('results-note').textContent=`No published measurements are available for this catalog dataset. It is excluded from rankings rather than treated as zero. ${columnCoverageNote}`;
|
| 84 |
} else if(scopeKey()==='float'&&!records.some(r=>r.scope==='float')) {
|
|
|
|
| 88 |
} 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.'} ${columnCoverageNote}`;
|
| 89 |
currentRows=aggregate(filteredRecords(true));
|
| 90 |
const weights=parseWeights($('weights').value);
|
| 91 |
+
$('score-note').textContent=`Score = ${weights[0].toFixed(1)} × average rate + ${weights[1].toFixed(1)} × normalized compress + ${weights[2].toFixed(1)} × normalized decompress. 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.`;
|
| 92 |
const s=state(),scope=scopeKey(),measuredCount=coverageDatasetCount(),ds=s.dataset==='all'?`all ${measuredCount} measured datasets`:datasetDisplayName(s.dataset);
|
| 93 |
$('dataset-count').textContent=countFmt(measuredCount);$('dataset-count-label').textContent='measured datasets';
|
| 94 |
const names={overall:'available numeric measurements',integer:'integer columns',float:'floating-point columns',fixed_float:'fixed-precision float columns',nonfixed_float:'non-fixed-precision float columns'};
|
|
|
|
| 115 |
if(!row){selectedMethod=null;detail.hidden=true;return;}selectedMethod=method;
|
| 116 |
const meta=methods.get(method)??methods.get(row.source[0].configuration), pipeline=meta?.operator_pipeline;
|
| 117 |
const source=row.source.slice().sort((a,b)=>a.dataset.localeCompare(b.dataset));
|
| 118 |
+
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>Compress ns/point</span></div><div><strong>${fmt(row.decompression_time,2)}</strong><span>Decompress 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. Measurement boundaries, source hashes, and available verification evidence are recorded in the manifest.</p><div class="detail-table"><table><thead><tr><th>Dataset</th><th>Configuration</th><th>Rate ↓</th><th>Compress ns/point ↓</th><th>Decompress 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>`;
|
| 119 |
detail.hidden=false;$('close-detail').addEventListener('click',()=>{detail.hidden=true;selectedMethod=null;});
|
| 120 |
if(focus){detail.scrollIntoView({block:'nearest',behavior:'auto'});detail.focus({preventScroll:true});}
|
| 121 |
}
|
|
|
|
| 130 |
let graph='';
|
| 131 |
for(let i=0;i<=5;i++){const rate=maxRate*i/5,px=x(rate);graph+=`<line class="gridline" x1="${px}" y1="${pad.top}" x2="${px}" y2="${height-pad.bottom}"/><text class="axis-label" x="${px}" y="${height-pad.bottom+25}" text-anchor="middle">${fmt(rate,2)}</text>`;}
|
| 132 |
for(let i=minLog;i<=maxLog;i++){const n=10**i,py=y(n);graph+=`<line class="gridline" x1="${pad.left}" y1="${py}" x2="${width-pad.right}" y2="${py}"/><text class="axis-label" x="${pad.left-12}" y="${py+4}" text-anchor="end">${n.toLocaleString('en-US',{maximumFractionDigits:3})}</text>`;}
|
| 133 |
+
for(const r of valid)graph+=`<circle class="point" tabindex="0" role="button" aria-label="${escape(r.method)}: compression rate ${fmt(r.compression_rate,4)}, ${metric==='compression_time'?'compression':'decompression'} ${fmt(r[metric],2)} ns per point" data-method="${escape(r.method)}" cx="${x(r.compression_rate)}" cy="${y(r[metric])}" r="6"><title>${escape(r.method)} | rate ${fmt(r.compression_rate,4)} | ${fmt(r[metric],2)} ns/point | coverage ${r.coverage}/${r.totalDatasets}</title></circle>`;
|
| 134 |
+
graph+=`<text class="axis-label" x="${width/2}" y="${height-13}" text-anchor="middle">Average compression rate (compressed / original) ↓</text><text class="axis-label" transform="translate(20 ${height/2}) rotate(-90)" text-anchor="middle">${metric==='compression_time'?'Compress':'Decompress'} ns/point (log scale) ↓</text>`;
|
| 135 |
+
$('chart').innerHTML=`<svg viewBox="0 0 ${width} ${height}" role="img" aria-label="Measured compression rates versus ${metric==='compression_time'?'compression':'decompression'} cost">${graph}</svg>`;
|
| 136 |
for(const p of $('chart').querySelectorAll('[data-method]')){p.addEventListener('click',()=>showMethod(p.dataset.method,true));p.addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();showMethod(p.dataset.method,true);}});}
|
| 137 |
}
|
| 138 |
function csv() {
|
| 139 |
const quote=v=>{let text=String(v??'');if(typeof v==='string'&&/^[=+@-]/.test(text))text="'"+text;return `"${text.replace(/"/g,'""')}"`;};
|
| 140 |
const weights=parseWeights($('weights').value);
|
| 141 |
+
const columns=['position','method','runtime','scope','row_mode','selected_dataset','sort_metric','sort_direction','average_compression_rate','overall_compression_rate','compress_ns_per_point','decompress_ns_per_point','weight_a','weight_b','weight_c','normalized_compress','normalized_decompress','composite_score','average_rank','rank_dataset_coverage','normalization_reference_rows','compress_min','compress_max','decompress_min','decompress_max','normalization','rank_definition','dataset_coverage','datasets_in_scope','selected_configuration_by_dataset','timing_column_count','timing_aggregation'];
|
| 142 |
+
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]))),r.timing_column_count,'arithmetic mean of per-column ns/point; equal weight per measured column'].map(quote).join(','));
|
| 143 |
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);
|
| 144 |
}
|
| 145 |
function safeHttpUrl(value) {
|
|
|
|
| 281 |
$('provenance').innerHTML=`<table class="provenance-table"><tbody>${Object.entries(info).map(([k,v])=>`<tr><th>${escape(k)}</th><td class="source-code">${escape(v)}</td></tr>`).join('')}</tbody></table>${exclusions.length?`<h3>Excluded or unavailable results</h3><p class="source-code">${escape(typeof exclusions==='string'?exclusions:JSON.stringify(exclusions))}</p>`:''}`;
|
| 282 |
}
|
| 283 |
function init() {
|
| 284 |
+
if(snapshot.compact_records){const t=snapshot.record_tables;snapshot.dataset_results=snapshot.compact_records.map(r=>({dataset:t.datasets[r[0]],scope:t.scopes[r[1]],runtime:t.runtimes[r[2]],family:t.families[r[3]],method:t.configurations[r[4]],algorithm_variant:t.configurations[r[4]],compression_rate:r[5],compression_time_ns_per_point:r[6],decompression_time_ns_per_point:r[7],original_size_bytes:r[8],compressed_size_bytes:r[9],timing_column_count:r[10]}));delete snapshot.compact_records;}
|
| 285 |
for(const m of snapshot.methods||[]) { const method=typeof m==='string'?{name:m}:m;methods.set(method.name??method.method,method);for(const c of method.configurations||[])methods.set(c.algorithm_variant,{...method,name:c.algorithm_variant,family:method.name}); }
|
| 286 |
for(const d of snapshot.datasets||[])datasets.set(d.key??d.dataset,d);
|
| 287 |
records=(snapshot.dataset_results||snapshot.records||[]).map(normalizeRecord).filter(r=>r.method&&r.dataset&&r.compression_rate>0);
|
|
|
|
| 313 |
setupTheme();
|
| 314 |
void loadReferenceCatalog();
|
| 315 |
void loadDatasetCatalog();
|
| 316 |
+
try { const response=await fetch('./ui-data.json?v=20261009-column-mean-v1',{cache:'no-cache'});if(!response.ok)throw new Error(`Snapshot request returned ${response.status}`);snapshot=await response.json();init(); }
|
| 317 |
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>';}
|
apply_dataset_scope.py
CHANGED
|
@@ -47,9 +47,10 @@ def apply() -> None:
|
|
| 47 |
r = dict(original)
|
| 48 |
# Preserve the exporter's 8-byte-equivalent denominator, including
|
| 49 |
# historical rows whose source byte totals are not divisible by eight.
|
| 50 |
-
r
|
| 51 |
-
|
| 52 |
-
|
|
|
|
| 53 |
configurations[r["runtime"], r["scope"], r["family"], r["method"]].append(r)
|
| 54 |
if r["is_best_rate_in_family"]:
|
| 55 |
families[r["runtime"], r["scope"], r["family"]].append(r)
|
|
|
|
| 47 |
r = dict(original)
|
| 48 |
# Preserve the exporter's 8-byte-equivalent denominator, including
|
| 49 |
# historical rows whose source byte totals are not divisible by eight.
|
| 50 |
+
r.setdefault("point_count", r["original_size_bytes"] / 8.0)
|
| 51 |
+
# A per-column arithmetic mean cannot be expanded back into elapsed
|
| 52 |
+
# time from a dataset point total. Leave weighted elapsed-time metrics
|
| 53 |
+
# unavailable unless the source row still carries the original totals.
|
| 54 |
configurations[r["runtime"], r["scope"], r["family"], r["method"]].append(r)
|
| 55 |
if r["is_best_rate_in_family"]:
|
| 56 |
families[r["runtime"], r["scope"], r["family"]].append(r)
|
build_ui_payload.py
CHANGED
|
@@ -20,7 +20,7 @@ def build() -> None:
|
|
| 20 |
compact.append([*(indices[key][r[field]] for key,field in fields.items()),
|
| 21 |
r['compression_rate'], r['compression_time_ns_per_point'],
|
| 22 |
r['decompression_time_ns_per_point'], r['original_size_bytes'],
|
| 23 |
-
r['compressed_size_bytes']])
|
| 24 |
payload = {key: data[key] for key in ('schema_version','benchmark','coverage','datasets','methods','scopes','source')}
|
| 25 |
if 'dataset_summary' in data:
|
| 26 |
payload['dataset_summary'] = data['dataset_summary']
|
|
|
|
| 20 |
compact.append([*(indices[key][r[field]] for key,field in fields.items()),
|
| 21 |
r['compression_rate'], r['compression_time_ns_per_point'],
|
| 22 |
r['decompression_time_ns_per_point'], r['original_size_bytes'],
|
| 23 |
+
r['compressed_size_bytes'], r.get('timing_column_count', 0)])
|
| 24 |
payload = {key: data[key] for key in ('schema_version','benchmark','coverage','datasets','methods','scopes','source')}
|
| 25 |
if 'dataset_summary' in data:
|
| 26 |
payload['dataset_summary'] = data['dataset_summary']
|
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:099f735c91c5ef4dff2b15ea2851caa6edd16f304f0063bfe6de948f475b4ba7
|
| 3 |
+
size 37791099
|
export_results.py
CHANGED
|
@@ -158,12 +158,27 @@ def _configuration_rows(
|
|
| 158 |
)
|
| 159 |
|
| 160 |
bundle_dir = BACKEND_DIR / "dataset" / dataset_key
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
if report is None or report.empty:
|
| 168 |
return []
|
| 169 |
if scope["data_type"] == "float":
|
|
@@ -217,8 +232,32 @@ def _configuration_rows(
|
|
| 217 |
original_bytes = _finite(record.originalSize, positive=True)
|
| 218 |
compressed_bytes = _finite(record.compressedSize, positive=True)
|
| 219 |
points = original_bytes / 8.0
|
| 220 |
-
|
| 221 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
rows.append(
|
| 223 |
{
|
| 224 |
"dataset": dataset_key,
|
|
@@ -231,11 +270,12 @@ def _configuration_rows(
|
|
| 231 |
"original_size_bytes": int(round(original_bytes)),
|
| 232 |
"compressed_size_bytes": int(round(compressed_bytes)),
|
| 233 |
"point_count": int(round(points)),
|
| 234 |
-
"compression_time_ns":
|
| 235 |
-
"decompression_time_ns":
|
| 236 |
"compression_rate": compressed_bytes / original_bytes,
|
| 237 |
-
"compression_time_ns_per_point":
|
| 238 |
-
"decompression_time_ns_per_point":
|
|
|
|
| 239 |
}
|
| 240 |
)
|
| 241 |
return sorted(rows, key=lambda row: (row["method"], row["algorithm_variant"]))
|
|
@@ -308,8 +348,33 @@ def _aggregate_rows(
|
|
| 308 |
original_sum = sum(row["original_size_bytes"] for row in rows)
|
| 309 |
compressed_sum = sum(row["compressed_size_bytes"] for row in rows)
|
| 310 |
point_sum = sum(row["point_count"] for row in rows)
|
| 311 |
-
|
| 312 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 313 |
return {
|
| 314 |
"runtime": runtime,
|
| 315 |
"scope": scope_key,
|
|
@@ -319,16 +384,15 @@ def _aggregate_rows(
|
|
| 319 |
"dataset_count": len(rows),
|
| 320 |
"overall_compression_rate": compressed_sum / original_sum,
|
| 321 |
"average_compression_rate": sum(row["compression_rate"] for row in rows) / len(rows),
|
| 322 |
-
"weighted_compression_time_ns_per_point":
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
/ len(rows),
|
| 332 |
"original_size_bytes": original_sum,
|
| 333 |
"compressed_size_bytes": compressed_sum,
|
| 334 |
"point_count": point_sum,
|
|
@@ -378,13 +442,10 @@ def main() -> int:
|
|
| 378 |
)
|
| 379 |
)
|
| 380 |
configuration_aggregates, family_best_aggregates = _aggregate_current_rows(dataset_rows)
|
| 381 |
-
#
|
| 382 |
-
#
|
| 383 |
-
#
|
| 384 |
-
|
| 385 |
-
row.pop("compression_time_ns", None)
|
| 386 |
-
row.pop("decompression_time_ns", None)
|
| 387 |
-
row.pop("point_count", None)
|
| 388 |
|
| 389 |
families = sorted({row["family"] for row in dataset_rows})
|
| 390 |
configurations = sorted(
|
|
@@ -466,7 +527,7 @@ def main() -> int:
|
|
| 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": "
|
| 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 |
"notes": [
|
| 472 |
"Missing combinations are unavailable rather than zero.",
|
|
@@ -477,7 +538,7 @@ def main() -> int:
|
|
| 477 |
"compression_rate": "compressed bytes divided by original numeric bytes; lower is better",
|
| 478 |
"average_compression_rate": "arithmetic mean of per-dataset compression rates",
|
| 479 |
"overall_compression_rate": "sum of compressed bytes divided by sum of original numeric bytes",
|
| 480 |
-
"average_time_ns_per_point": "arithmetic mean of per-
|
| 481 |
"weighted_time_ns_per_point": "sum of measured nanoseconds divided by sum of 8-byte numeric points",
|
| 482 |
"paper_median_balanced_score": "stored paper snapshot value; normalization is paper-scope specific and lower is better",
|
| 483 |
},
|
|
|
|
| 158 |
)
|
| 159 |
|
| 160 |
bundle_dir = BACKEND_DIR / "dataset" / dataset_key
|
| 161 |
+
if scope["data_type"] == "overall":
|
| 162 |
+
report_parts = []
|
| 163 |
+
for report_type in ("int", "float"):
|
| 164 |
+
part = read_benchmark_report(
|
| 165 |
+
bundle_dir, dataset_key, data_type=report_type, implement_language=runtime
|
| 166 |
+
)
|
| 167 |
+
if part is not None and not part.empty:
|
| 168 |
+
part = part.copy()
|
| 169 |
+
part["_timing_source_type"] = report_type
|
| 170 |
+
report_parts.append(part)
|
| 171 |
+
report = pd.concat(report_parts, ignore_index=True) if report_parts else None
|
| 172 |
+
else:
|
| 173 |
+
report = read_benchmark_report(
|
| 174 |
+
bundle_dir,
|
| 175 |
+
dataset_key,
|
| 176 |
+
data_type=scope["data_type"],
|
| 177 |
+
implement_language=runtime,
|
| 178 |
+
)
|
| 179 |
+
if report is not None and not report.empty:
|
| 180 |
+
report = report.copy()
|
| 181 |
+
report["_timing_source_type"] = scope["data_type"]
|
| 182 |
if report is None or report.empty:
|
| 183 |
return []
|
| 184 |
if scope["data_type"] == "float":
|
|
|
|
| 232 |
original_bytes = _finite(record.originalSize, positive=True)
|
| 233 |
compressed_bytes = _finite(record.compressedSize, positive=True)
|
| 234 |
points = original_bytes / 8.0
|
| 235 |
+
timing_rows = work[
|
| 236 |
+
(work["algorithm"].astype(str) == str(record.algorithm))
|
| 237 |
+
& (pd.to_numeric(work["column_index"], errors="coerce") >= 0)
|
| 238 |
+
].copy()
|
| 239 |
+
timing_column_count = len(timing_rows)
|
| 240 |
+
if timing_column_count:
|
| 241 |
+
timing_columns = timing_rows.groupby(
|
| 242 |
+
["_timing_source_type", "column_index"], as_index=False
|
| 243 |
+
).agg(
|
| 244 |
+
originalSize=("originalSize", "sum"),
|
| 245 |
+
compressionTime=("compressionTime", "sum"),
|
| 246 |
+
decompressionTime=("decompressionTime", "sum"),
|
| 247 |
+
)
|
| 248 |
+
timing_column_count = len(timing_columns)
|
| 249 |
+
timing_points = timing_columns["originalSize"] / 8.0
|
| 250 |
+
compression_time_ns_per_point = float(
|
| 251 |
+
(timing_columns["compressionTime"] / timing_points).mean()
|
| 252 |
+
)
|
| 253 |
+
decompression_time_ns_per_point = float(
|
| 254 |
+
(timing_columns["decompressionTime"] / timing_points).mean()
|
| 255 |
+
)
|
| 256 |
+
else:
|
| 257 |
+
# A column_index=-1 row is a whole-report aggregate. It cannot prove
|
| 258 |
+
# the requested arithmetic mean of individual column timings.
|
| 259 |
+
compression_time_ns_per_point = None
|
| 260 |
+
decompression_time_ns_per_point = None
|
| 261 |
rows.append(
|
| 262 |
{
|
| 263 |
"dataset": dataset_key,
|
|
|
|
| 270 |
"original_size_bytes": int(round(original_bytes)),
|
| 271 |
"compressed_size_bytes": int(round(compressed_bytes)),
|
| 272 |
"point_count": int(round(points)),
|
| 273 |
+
"compression_time_ns": _finite(record.compressionTime),
|
| 274 |
+
"decompression_time_ns": _finite(record.decompressionTime),
|
| 275 |
"compression_rate": compressed_bytes / original_bytes,
|
| 276 |
+
"compression_time_ns_per_point": compression_time_ns_per_point,
|
| 277 |
+
"decompression_time_ns_per_point": decompression_time_ns_per_point,
|
| 278 |
+
"timing_column_count": timing_column_count,
|
| 279 |
}
|
| 280 |
)
|
| 281 |
return sorted(rows, key=lambda row: (row["method"], row["algorithm_variant"]))
|
|
|
|
| 348 |
original_sum = sum(row["original_size_bytes"] for row in rows)
|
| 349 |
compressed_sum = sum(row["compressed_size_bytes"] for row in rows)
|
| 350 |
point_sum = sum(row["point_count"] for row in rows)
|
| 351 |
+
elapsed_rows = [
|
| 352 |
+
row for row in rows
|
| 353 |
+
if row.get("point_count", 0) > 0
|
| 354 |
+
and row.get("compression_time_ns") is not None
|
| 355 |
+
and row.get("decompression_time_ns") is not None
|
| 356 |
+
]
|
| 357 |
+
elapsed_point_sum = sum(row["point_count"] for row in elapsed_rows)
|
| 358 |
+
comp_time_sum = sum(row["compression_time_ns"] for row in elapsed_rows)
|
| 359 |
+
decomp_time_sum = sum(row["decompression_time_ns"] for row in elapsed_rows)
|
| 360 |
+
timing_rows = [
|
| 361 |
+
row for row in rows
|
| 362 |
+
if row.get("timing_column_count", 0) > 0
|
| 363 |
+
and row.get("compression_time_ns_per_point") is not None
|
| 364 |
+
and row.get("decompression_time_ns_per_point") is not None
|
| 365 |
+
]
|
| 366 |
+
timing_column_count = sum(row["timing_column_count"] for row in timing_rows)
|
| 367 |
+
timing_complete = len(timing_rows) == len(rows)
|
| 368 |
+
average_compression_time = (
|
| 369 |
+
sum(row["compression_time_ns_per_point"] * row["timing_column_count"] for row in timing_rows)
|
| 370 |
+
/ timing_column_count
|
| 371 |
+
if timing_column_count and timing_complete else None
|
| 372 |
+
)
|
| 373 |
+
average_decompression_time = (
|
| 374 |
+
sum(row["decompression_time_ns_per_point"] * row["timing_column_count"] for row in timing_rows)
|
| 375 |
+
/ timing_column_count
|
| 376 |
+
if timing_column_count and timing_complete else None
|
| 377 |
+
)
|
| 378 |
return {
|
| 379 |
"runtime": runtime,
|
| 380 |
"scope": scope_key,
|
|
|
|
| 384 |
"dataset_count": len(rows),
|
| 385 |
"overall_compression_rate": compressed_sum / original_sum,
|
| 386 |
"average_compression_rate": sum(row["compression_rate"] for row in rows) / len(rows),
|
| 387 |
+
"weighted_compression_time_ns_per_point": (
|
| 388 |
+
comp_time_sum / elapsed_point_sum if elapsed_point_sum else None
|
| 389 |
+
),
|
| 390 |
+
"average_compression_time_ns_per_point": average_compression_time,
|
| 391 |
+
"weighted_decompression_time_ns_per_point": (
|
| 392 |
+
decomp_time_sum / elapsed_point_sum if elapsed_point_sum else None
|
| 393 |
+
),
|
| 394 |
+
"average_decompression_time_ns_per_point": average_decompression_time,
|
| 395 |
+
"timing_column_count": timing_column_count,
|
|
|
|
| 396 |
"original_size_bytes": original_sum,
|
| 397 |
"compressed_size_bytes": compressed_sum,
|
| 398 |
"point_count": point_sum,
|
|
|
|
| 442 |
)
|
| 443 |
)
|
| 444 |
configuration_aggregates, family_best_aggregates = _aggregate_current_rows(dataset_rows)
|
| 445 |
+
# Retain the source elapsed-time and point totals. A per-column arithmetic
|
| 446 |
+
# mean cannot be expanded back into elapsed time from a dataset point total,
|
| 447 |
+
# so later scope transforms must use these measured totals rather than infer
|
| 448 |
+
# them from the displayed mean.
|
|
|
|
|
|
|
|
|
|
| 449 |
|
| 450 |
families = sorted({row["family"] for row in dataset_rows})
|
| 451 |
configurations = sorted(
|
|
|
|
| 527 |
"current_result_selection": "visible configurations of methods categorized as lossless-design by the project; result-level float round trips are not established",
|
| 528 |
"paper_result_selection": "the separate paper_table3 section contains its frozen 20-method C++ snapshot",
|
| 529 |
"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.",
|
| 530 |
+
"aggregation_note": "Compression and decompression time averages are arithmetic means of available per-column nanoseconds per point. Dataset rows without column-level timing evidence are unavailable for timing; any aggregate containing one of these rows also has unavailable timing and is excluded from timing-based scores. Overall compression rate and weighted times use summed measured bytes, points, and nanoseconds only.",
|
| 531 |
"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.",
|
| 532 |
"notes": [
|
| 533 |
"Missing combinations are unavailable rather than zero.",
|
|
|
|
| 538 |
"compression_rate": "compressed bytes divided by original numeric bytes; lower is better",
|
| 539 |
"average_compression_rate": "arithmetic mean of per-dataset compression rates",
|
| 540 |
"overall_compression_rate": "sum of compressed bytes divided by sum of original numeric bytes",
|
| 541 |
+
"average_time_ns_per_point": "arithmetic mean of per-column nanoseconds per numeric point; legacy CSV columns retain their 8-byte-equivalent point denominator",
|
| 542 |
"weighted_time_ns_per_point": "sum of measured nanoseconds divided by sum of 8-byte numeric points",
|
| 543 |
"paper_median_balanced_score": "stored paper snapshot value; normalization is paper-scope specific and lower is better",
|
| 544 |
},
|
index.html
CHANGED
|
@@ -3,11 +3,11 @@
|
|
| 3 |
<head>
|
| 4 |
<meta charset="utf-8">
|
| 5 |
<meta name="viewport" content="width=device-width,initial-scale=1">
|
| 6 |
-
<meta name="description" content="THULab time-series compression benchmark: measured compression rates,
|
| 7 |
<meta name="color-scheme" content="light dark">
|
| 8 |
<title>THULab | Time Series Compression Benchmark</title>
|
| 9 |
<link rel="stylesheet" href="style.css?v=20261009-dbp-integrated-v1">
|
| 10 |
-
<script src="app.js?v=20261009-
|
| 11 |
</head>
|
| 12 |
<body>
|
| 13 |
<a class="skip-link" href="#content">Skip to results</a>
|
|
@@ -19,7 +19,7 @@
|
|
| 19 |
<section class="intro" aria-labelledby="title">
|
| 20 |
<p class="eyebrow">Time-series systems research</p>
|
| 21 |
<h1 id="title">Time Series Compression Leaderboard</h1>
|
| 22 |
-
<p class="intro-text">Compare published storage,
|
| 23 |
<p class="snapshot" id="snapshot">Loading benchmark snapshot…</p>
|
| 24 |
</section>
|
| 25 |
<div class="summary-strip" id="summary" aria-label="Snapshot coverage">
|
|
@@ -62,9 +62,9 @@
|
|
| 62 |
<div><h2 id="view-title">Overall leaderboard</h2><p id="selection-summary" class="muted" aria-live="polite">Reading published results…</p></div>
|
| 63 |
<div class="toolbar-actions"><button id="reset" class="quiet-button">Reset filters</button><button id="download" class="primary-button" disabled>Download CSV</button></div>
|
| 64 |
</div>
|
| 65 |
-
<div class="results-note" id="results-note">Compression rate = compressed bytes / original bytes. Lower rates and lower ns/point are better.
|
| 66 |
<div id="score-controls" class="score-controls">
|
| 67 |
-
<label>Score weights · a: rate / b:
|
| 68 |
<p id="score-note" class="muted" aria-live="polite">Non-negative weights in steps of 0.1, summing to 1.0.</p>
|
| 69 |
</div>
|
| 70 |
<div class="table-settings">
|
|
@@ -72,7 +72,7 @@
|
|
| 72 |
<details><summary>Display columns</summary><div id="column-options" class="column-options"></div></details>
|
| 73 |
</div>
|
| 74 |
<div id="chart-view" hidden>
|
| 75 |
-
<div class="chart-controls"><label>Vertical axis<select id="chart-metric"><option value="compression_time">
|
| 76 |
<div id="chart" class="chart" aria-label="Compression rate and execution cost scatter plot"></div>
|
| 77 |
</div>
|
| 78 |
<div id="leaderboard-wrap" class="table-wrap" tabindex="0" aria-label="Scrollable sortable leaderboard">
|
|
@@ -108,9 +108,9 @@
|
|
| 108 |
<dl class="definitions">
|
| 109 |
<div><dt>Average compression rate ↓</dt><dd>Arithmetic mean of the per-dataset compression rates in the selected scope.</dd></div>
|
| 110 |
<div><dt>Overall compression rate ↓</dt><dd>Total compressed bytes divided by total original bytes over the selected measured records.</dd></div>
|
| 111 |
-
<div><dt>
|
| 112 |
<div><dt>Coverage</dt><dd>Datasets with usable measurements for a method out of the measured datasets in the selected runtime, data type, and dataset selection. The summary dataset count uses the same selection. Missing results are never filled with zero.</dd></div>
|
| 113 |
-
<div><dt>Composite score ↓</dt><dd>a × average compression rate + b × normalized average
|
| 114 |
<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 non-negative tenths summing to 1.0. Missing measurements do not receive ranks.</dd></div>
|
| 115 |
</dl>
|
| 116 |
<h3>Reading the rankings</h3>
|
|
@@ -121,7 +121,7 @@
|
|
| 121 |
<p><a href="data.json" download>Result snapshot (JSON)</a> · <a href="manifest.json" download>Source manifest (JSON)</a></p>
|
| 122 |
<div id="provenance"></div>
|
| 123 |
<h3>Contribute a result</h3>
|
| 124 |
-
<p>Run the project benchmark and provide the codec/configuration, input identity,
|
| 125 |
</section>
|
| 126 |
<noscript><p class="error-message">Enable JavaScript to filter and sort this leaderboard. You can also download <a href="data.json">the results JSON</a>.</p></noscript>
|
| 127 |
<footer><span>THULab · Time Series Compression Benchmark</span><span id="footer-snapshot"></span></footer>
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="utf-8">
|
| 5 |
<meta name="viewport" content="width=device-width,initial-scale=1">
|
| 6 |
+
<meta name="description" content="THULab time-series compression benchmark: measured compression rates, compression and decompression 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=20261009-dbp-integrated-v1">
|
| 10 |
+
<script src="app.js?v=20261009-column-mean-v1" type="module"></script>
|
| 11 |
</head>
|
| 12 |
<body>
|
| 13 |
<a class="skip-link" href="#content">Skip to results</a>
|
|
|
|
| 19 |
<section class="intro" aria-labelledby="title">
|
| 20 |
<p class="eyebrow">Time-series systems research</p>
|
| 21 |
<h1 id="title">Time Series Compression Leaderboard</h1>
|
| 22 |
+
<p class="intro-text">Compare published storage, compression, and decompression measurements across time-series datasets.</p>
|
| 23 |
<p class="snapshot" id="snapshot">Loading benchmark snapshot…</p>
|
| 24 |
</section>
|
| 25 |
<div class="summary-strip" id="summary" aria-label="Snapshot coverage">
|
|
|
|
| 62 |
<div><h2 id="view-title">Overall leaderboard</h2><p id="selection-summary" class="muted" aria-live="polite">Reading published results…</p></div>
|
| 63 |
<div class="toolbar-actions"><button id="reset" class="quiet-button">Reset filters</button><button id="download" class="primary-button" disabled>Download CSV</button></div>
|
| 64 |
</div>
|
| 65 |
+
<div class="results-note" id="results-note">Compression rate = compressed bytes / original bytes. Lower rates and lower ns/point are better. Compression and decompression costs are the arithmetic mean of the measured columns' ns/point values; column coverage may differ. Historical aggregate-only timings without per-column evidence are not inferred.</div>
|
| 66 |
<div id="score-controls" class="score-controls">
|
| 67 |
+
<label>Score weights · a: rate / b: compress / c: decompress<select id="weights" aria-label="Score weights a b c"></select></label>
|
| 68 |
<p id="score-note" class="muted" aria-live="polite">Non-negative weights in steps of 0.1, summing to 1.0.</p>
|
| 69 |
</div>
|
| 70 |
<div class="table-settings">
|
|
|
|
| 72 |
<details><summary>Display columns</summary><div id="column-options" class="column-options"></div></details>
|
| 73 |
</div>
|
| 74 |
<div id="chart-view" hidden>
|
| 75 |
+
<div class="chart-controls"><label>Vertical axis<select id="chart-metric"><option value="compression_time">Compression cost (ns/point)</option><option value="decompression_time">Decompression cost (ns/point)</option></select></label><span class="muted">Lower and further left is better. Click a point for method details.</span></div>
|
| 76 |
<div id="chart" class="chart" aria-label="Compression rate and execution cost scatter plot"></div>
|
| 77 |
</div>
|
| 78 |
<div id="leaderboard-wrap" class="table-wrap" tabindex="0" aria-label="Scrollable sortable leaderboard">
|
|
|
|
| 108 |
<dl class="definitions">
|
| 109 |
<div><dt>Average compression rate ↓</dt><dd>Arithmetic mean of the per-dataset compression rates in the selected scope.</dd></div>
|
| 110 |
<div><dt>Overall compression rate ↓</dt><dd>Total compressed bytes divided by total original bytes over the selected measured records.</dd></div>
|
| 111 |
+
<div><dt>Compression / decompression cost ↓</dt><dd>For each result with column-level timing evidence, calculate ns/point independently for every usable numeric column, then take the arithmetic mean across those columns. Each column contributes equally. Overall timing pools all measured columns across the selected datasets; if any selected dataset/configuration lacks column-level timing evidence, that timing is Not reported. Historical aggregate-only timing rows without recoverable per-column values are not inferred. Native-float measurements use valid native values; historical imported rows retain their source 8-byte-equivalent point definition. Compare timings within the same runtime and measurement contract.</dd></div>
|
| 112 |
<div><dt>Coverage</dt><dd>Datasets with usable measurements for a method out of the measured datasets in the selected runtime, data type, and dataset selection. The summary dataset count uses the same selection. Missing results are never filled with zero.</dd></div>
|
| 113 |
+
<div><dt>Composite score ↓</dt><dd>a × average compression rate + b × normalized average compression time + c × normalized average decompression 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>
|
| 114 |
<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 non-negative tenths summing to 1.0. Missing measurements do not receive ranks.</dd></div>
|
| 115 |
</dl>
|
| 116 |
<h3>Reading the rankings</h3>
|
|
|
|
| 121 |
<p><a href="data.json" download>Result snapshot (JSON)</a> · <a href="manifest.json" download>Source manifest (JSON)</a></p>
|
| 122 |
<div id="provenance"></div>
|
| 123 |
<h3>Contribute a result</h3>
|
| 124 |
+
<p>Run the project benchmark and provide the codec/configuration, input identity, compression and decompression measurements, execution environment, and round-trip validation evidence. Results can be updated from the source project using the snapshot exporter included in the Space repository.</p>
|
| 125 |
</section>
|
| 126 |
<noscript><p class="error-message">Enable JavaScript to filter and sort this leaderboard. You can also download <a href="data.json">the results JSON</a>.</p></noscript>
|
| 127 |
<footer><span>THULab · Time Series Compression Benchmark</span><span id="footer-snapshot"></span></footer>
|
manifest.json
CHANGED
|
@@ -25,10 +25,13 @@
|
|
| 25 |
"python"
|
| 26 |
],
|
| 27 |
"scope_count": 5,
|
| 28 |
-
"source_report_file_count": 108
|
|
|
|
|
|
|
|
|
|
| 29 |
},
|
| 30 |
"data_file": "data.json",
|
| 31 |
-
"data_sha256": "
|
| 32 |
"dataset_catalog_sha256": "d69abc85742dc050e4cdab1e5a03fd3392a0e0f1964b2a88ca87ba7f768b7385",
|
| 33 |
"excluded_content": [
|
| 34 |
"raw datasets",
|
|
@@ -762,5 +765,16 @@
|
|
| 762 |
"sha256": "d69abc85742dc050e4cdab1e5a03fd3392a0e0f1964b2a88ca87ba7f768b7385",
|
| 763 |
"status": "reviewed_public_catalog"
|
| 764 |
}
|
| 765 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 766 |
}
|
|
|
|
| 25 |
"python"
|
| 26 |
],
|
| 27 |
"scope_count": 5,
|
| 28 |
+
"source_report_file_count": 108,
|
| 29 |
+
"timing_column_mean_dataset_result_count": 64879,
|
| 30 |
+
"timing_column_observation_count": 1435539,
|
| 31 |
+
"timing_unavailable_dataset_result_count": 340
|
| 32 |
},
|
| 33 |
"data_file": "data.json",
|
| 34 |
+
"data_sha256": "099f735c91c5ef4dff2b15ea2851caa6edd16f304f0063bfe6de948f475b4ba7",
|
| 35 |
"dataset_catalog_sha256": "d69abc85742dc050e4cdab1e5a03fd3392a0e0f1964b2a88ca87ba7f768b7385",
|
| 36 |
"excluded_content": [
|
| 37 |
"raw datasets",
|
|
|
|
| 765 |
"sha256": "d69abc85742dc050e4cdab1e5a03fd3392a0e0f1964b2a88ca87ba7f768b7385",
|
| 766 |
"status": "reviewed_public_catalog"
|
| 767 |
}
|
| 768 |
+
],
|
| 769 |
+
"timing_recalculation": {
|
| 770 |
+
"aggregate_only_rows_excluded_from_timing": 340,
|
| 771 |
+
"dbp_float_audit_path": "analysis_outputs/dbp_float_e2e_20261009/publish_payload/dbp-audit.json",
|
| 772 |
+
"dbp_float_audit_sha256": "febe0d4494cf3778bb3e386ac8674f24406e29d1d7d9b47bd834b332b0e201fb",
|
| 773 |
+
"dbp_precision_source_file_count": 376,
|
| 774 |
+
"dbp_precision_source_hash_status": "all_match",
|
| 775 |
+
"generated_at": "2026-10-09T06:52:14.348090+00:00",
|
| 776 |
+
"policy": "arithmetic_mean_of_per_column_ns_per_point",
|
| 777 |
+
"result_csv_file_count": 108,
|
| 778 |
+
"result_csv_sha256_status": "all_match"
|
| 779 |
+
}
|
| 780 |
}
|
ui-data.json
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|