jzxiao commited on
Commit
26386d8
·
verified ·
1 Parent(s): 43d6fa3

Complete medium_posts results and restore dataset timing averages

Browse files

Integrate the verified C++, Java, and Python medium_posts measurement into the existing leaderboard. Use equal-dataset timing averages when selected results contain aggregate-only timing evidence, while retaining equal-column averages for fully column-backed results. Refresh scores, ranks, compact data, and provenance.

Files changed (9) hide show
  1. README.md +11 -3
  2. app.js +15 -8
  3. apply_dataset_scope.py +12 -0
  4. build_ui_payload.py +3 -1
  5. data.json +2 -2
  6. export_results.py +93 -47
  7. index.html +3 -3
  8. manifest.json +69 -9
  9. ui-data.json +0 -0
README.md CHANGED
@@ -24,8 +24,13 @@ 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
- # 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
 
@@ -47,6 +52,9 @@ The summary dataset count and Dataset coverage denominator use the same measured
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
 
 
 
 
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 source identities, rebuild timing averages, and reapply the targeted retest.
28
+ python3 scripts/update_site_column_means.py \
29
+ --receipt /path/to/timing-fallback-receipt.json \
30
+ --dbp-medium-payload analysis_outputs/column_mean_20261009/dbp_medium_posts_retest/integration_payload.json \
31
+ --dbp-medium-payload-sha256 e45170a955d1cd0df50dba25f9e41f24f00fc8b559e702d59188b6cfa60c99dc \
32
+ --dbp-medium-receipt analysis_outputs/column_mean_20261009/dbp_medium_posts_retest/measurement_receipt.json \
33
+ --dbp-medium-receipt-sha256 10359f6f1153655710f7ce9b59d04e5460babe5606487b7794a47ba874ef4324
34
  python3 deploy/huggingface-compressionbenchmark/build_ui_payload.py
35
  ```
36
 
 
52
 
53
  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`.
54
 
55
+ 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 when every selected dataset has column-level evidence. If any selected dataset/configuration is backed only by aggregate timing, the method uses an arithmetic mean of its selected per-dataset ns/point values, giving every dataset equal weight. Aggregate-only dataset timing uses recorded elapsed time divided by recorded points; these records keep a zero measured-column count. Truly missing measurements remain unavailable. Method details and CSV expose the timing basis. 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.
56
 
57
  Source project: https://github.com/xjz17/web_compression
58
+
59
+
60
+ The timing updater verifies the input hashes, replaces existing dataset/runtime/scope rows by identity, and adds only missing rows. The measurement harness and its frozen-source instructions are in `scripts/dbp_medium_posts_retest/`; raw data and compiled binaries are excluded from Git.
app.js CHANGED
@@ -3,10 +3,17 @@ const $ = (id) => document.getElementById(id);
3
  const escape = (value) => String(value ?? '').replace(/[&<>"']/g, (c) => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[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);
@@ -55,7 +62,7 @@ function aggregate(input) {
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)=>{
@@ -78,7 +85,7 @@ function renderTable() {
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')) {
@@ -115,7 +122,7 @@ function showMethod(method,focus=false) {
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
  }
@@ -139,7 +146,7 @@ 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,7 +288,7 @@ function renderAbout() {
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,5 +320,5 @@ function setupTheme() {
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>';}
 
3
  const escape = (value) => String(value ?? '').replace(/[&<>"']/g, (c) => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[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 timingMode(input) {
7
+ if(!input.length||input.some(r=>number(r.compression_time)===null||number(r.decompression_time)===null||!Number.isInteger(r.timing_column_count)||r.timing_column_count<0||!((r.timing_aggregation==='column_mean'&&r.timing_column_count>0)||(r.timing_aggregation==='dataset_aggregate'&&r.timing_column_count===0))))return 'unavailable';
8
+ return input.some(r=>r.timing_aggregation==='dataset_aggregate')?'dataset_mean':'column_mean';
9
+ }
10
+ function aggregateTime(input,key) {
11
+ const mode=timingMode(input);
12
+ if(mode==='unavailable')return null;
13
+ if(mode==='dataset_mean')return mean(input.map(r=>r[key]));
14
  return input.reduce((sum,r)=>sum+r[key]*r.timing_column_count,0)/input.reduce((sum,r)=>sum+r.timing_column_count,0);
15
  }
16
+ const timingLabel=mode=>({column_mean:'Equal-column arithmetic mean',dataset_mean:'Equal-dataset arithmetic mean',dataset_aggregate:'Dataset aggregate',unavailable:'Not reported'}[mode]??'Not reported');
17
  const fmt = (v, digits=3) => number(v) === null ? 'Not reported' : Number(v).toLocaleString('en-US',{maximumFractionDigits:digits,minimumFractionDigits:digits});
18
  const countFmt = (v) => Number(v).toLocaleString('en-US');
19
  const runtimeName = (v) => ({cpp:'C++',java:'Java',python:'Python'}[v] || v);
 
62
  for(const [method,source] of groups) {
63
  const hasBytes=source.every(r=>r.original_bytes!==null && r.original_bytes>0 && r.compressed_bytes!==null && r.compressed_bytes>0);
64
  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;
65
+ rows.push({method,family:source[0].family,runtime:source[0].runtime,compression_rate:mean(source.map(r=>r.compression_rate)),compression_time:aggregateTime(source,'compression_time'),decompression_time:aggregateTime(source,'decompression_time'),timing_column_count:source.reduce((sum,r)=>sum+r.timing_column_count,0),timing_aggregation:timingMode(source),overall_rate:hasBytes?compressed/original:null,original_bytes:original,compressed_bytes:compressed,coverage:new Set(source.map(r=>r.dataset)).size,totalDatasets,source});
66
  }
67
  const s=state();
68
  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)=>{
 
85
  function refresh() {
86
  if(!snapshot)return;
87
  $('precision').disabled=$('dtype').value!=='float';
88
+ const selectedDataset=state().dataset,selectedHasMeasurements=selectedDataset==='all'||records.some(r=>r.dataset===selectedDataset),columnCoverageNote='Compress and Decompress times use equal-column arithmetic means when every selected dataset has column timings. If a method has aggregate-only timings in the selection, its dataset ns/point values are averaged with equal weight per dataset. Timing basis is shown in method details and CSV. Column coverage may differ by method.';
89
  if(!selectedHasMeasurements) {
90
  $('results-note').textContent=`No published measurements are available for this catalog dataset. It is excluded from rankings rather than treated as zero. ${columnCoverageNote}`;
91
  } else if(scopeKey()==='float'&&!records.some(r=>r.scope==='float')) {
 
122
  if(!row){selectedMethod=null;detail.hidden=true;return;}selectedMethod=method;
123
  const meta=methods.get(method)??methods.get(row.source[0].configuration), pipeline=meta?.operator_pipeline;
124
  const source=row.source.slice().sort((a,b)=>a.dataset.localeCompare(b.dataset));
125
+ 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">Timing average: ${escape(timingLabel(row.timing_aggregation))}. 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>Timing basis</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>${escape(timingLabel(r.timing_aggregation))}</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>`;
126
  detail.hidden=false;$('close-detail').addEventListener('click',()=>{detail.hidden=true;selectedMethod=null;});
127
  if(focus){detail.scrollIntoView({block:'nearest',behavior:'auto'});detail.focus({preventScroll:true});}
128
  }
 
146
  const quote=v=>{let text=String(v??'');if(typeof v==='string'&&/^[=+@-]/.test(text))text="'"+text;return `"${text.replace(/"/g,'""')}"`;};
147
  const weights=parseWeights($('weights').value);
148
  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'];
149
+ 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,r.timing_aggregation].map(quote).join(','));
150
  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);
151
  }
152
  function safeHttpUrl(value) {
 
288
  $('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>`:''}`;
289
  }
290
  function init() {
291
+ 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],timing_aggregation:t.timing_aggregations[r[11]]}));delete snapshot.compact_records;}
292
  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}); }
293
  for(const d of snapshot.datasets||[])datasets.set(d.key??d.dataset,d);
294
  records=(snapshot.dataset_results||snapshot.records||[]).map(normalizeRecord).filter(r=>r.method&&r.dataset&&r.compression_rate>0);
 
320
  setupTheme();
321
  void loadReferenceCatalog();
322
  void loadDatasetCatalog();
323
+ try { const response=await fetch('./ui-data.json?v=20261009-dataset-fallback-v1',{cache:'no-cache'});if(!response.ok)throw new Error(`Snapshot request returned ${response.status}`);snapshot=await response.json();init(); }
324
  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
@@ -93,6 +93,18 @@ def apply() -> None:
93
  runtime_configuration_count=len({(r["runtime"], r["method"]) for r in rows}),
94
  source_report_file_count=len(source["result_csv_files_used"]), paper_leaderboard_row_count=0,
95
  excluded_datasets=sorted(excluded | set(coverage.get("excluded_datasets", []))))
 
 
 
 
 
 
 
 
 
 
 
 
96
  data["dataset_summary"] = {**catalog["summary"], "published_measurement_dataset_count": len(measured)}
97
  data_path.write_text(json.dumps(data, ensure_ascii=False, indent=2, sort_keys=True, allow_nan=False) + "\n")
98
  manifest_path = ROOT / "manifest.json"
 
93
  runtime_configuration_count=len({(r["runtime"], r["method"]) for r in rows}),
94
  source_report_file_count=len(source["result_csv_files_used"]), paper_leaderboard_row_count=0,
95
  excluded_datasets=sorted(excluded | set(coverage.get("excluded_datasets", []))))
96
+ coverage.update(
97
+ timing_column_observation_count=sum(r.get("timing_column_count", 0) for r in rows),
98
+ timing_column_mean_dataset_result_count=sum(
99
+ r.get("timing_aggregation") == "column_mean" for r in rows
100
+ ),
101
+ timing_dataset_aggregate_result_count=sum(
102
+ r.get("timing_aggregation") == "dataset_aggregate" for r in rows
103
+ ),
104
+ timing_unavailable_dataset_result_count=sum(
105
+ r.get("timing_aggregation") == "unavailable" for r in rows
106
+ ),
107
+ )
108
  data["dataset_summary"] = {**catalog["summary"], "published_measurement_dataset_count": len(measured)}
109
  data_path.write_text(json.dumps(data, ensure_ascii=False, indent=2, sort_keys=True, allow_nan=False) + "\n")
110
  manifest_path = ROOT / "manifest.json"
build_ui_payload.py CHANGED
@@ -14,13 +14,15 @@ def build() -> None:
14
  fields = {'datasets': 'dataset', 'scopes': 'scope', 'runtimes': 'runtime',
15
  'families': 'family', 'configurations': 'algorithm_variant'}
16
  tables = {key: sorted({r[field] for r in rows}) for key, field in fields.items()}
 
17
  indices = {key: {v:i for i,v in enumerate(values)} for key,values in tables.items()}
18
  compact = []
19
  for r in rows:
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']
 
14
  fields = {'datasets': 'dataset', 'scopes': 'scope', 'runtimes': 'runtime',
15
  'families': 'family', 'configurations': 'algorithm_variant'}
16
  tables = {key: sorted({r[field] for r in rows}) for key, field in fields.items()}
17
+ tables['timing_aggregations'] = sorted({r['timing_aggregation'] for r in rows})
18
  indices = {key: {v:i for i,v in enumerate(values)} for key,values in tables.items()}
19
  compact = []
20
  for r in rows:
21
  compact.append([*(indices[key][r[field]] for key,field in fields.items()),
22
  r['compression_rate'], r['compression_time_ns_per_point'],
23
  r['decompression_time_ns_per_point'], r['original_size_bytes'],
24
+ r['compressed_size_bytes'], r.get('timing_column_count', 0),
25
+ indices['timing_aggregations'][r['timing_aggregation']]])
26
  payload = {key: data[key] for key in ('schema_version','benchmark','coverage','datasets','methods','scopes','source')}
27
  if 'dataset_summary' in data:
28
  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:099f735c91c5ef4dff2b15ea2851caa6edd16f304f0063bfe6de948f475b4ba7
3
- size 37791099
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:727bf6a91dbb2bc8f63dd87122b8dc1db261589e4da742f28f0e52dad0616571
3
+ size 41080819
export_results.py CHANGED
@@ -74,6 +74,52 @@ def _finite(value: Any, *, positive: bool = False) -> float:
74
  return number
75
 
76
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  @lru_cache(maxsize=None)
78
  def _algorithm_metadata(algorithm_key: str) -> tuple[bool, str | None, bool]:
79
  from myapp.home_leaderboard_boxplot_data import (
@@ -233,31 +279,9 @@ def _configuration_rows(
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,
@@ -273,9 +297,10 @@ def _configuration_rows(
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"]))
@@ -357,24 +382,32 @@ def _aggregate_rows(
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,
@@ -385,14 +418,16 @@ def _aggregate_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,
@@ -527,7 +562,7 @@ def main() -> int:
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,7 +573,7 @@ def main() -> int:
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
  },
@@ -547,6 +582,7 @@ def main() -> int:
547
  "No round-trip verification claim is exported from timing/result CSVs alone.",
548
  "Family-level aggregates select the measured variant with the lowest compression rate within each dataset and method family, matching the backend family-selection rule.",
549
  "dataset_results contains every visible measured configuration; is_best_rate_in_family marks the configuration used for family-level aggregation.",
 
550
  "Paper Table 3 values and current report recomputations are separate result sets because the report tree has evolved since the paper snapshot was frozen.",
551
  ],
552
  },
@@ -570,6 +606,16 @@ def main() -> int:
570
  "configuration_aggregate_row_count": len(configuration_aggregates),
571
  "family_best_aggregate_row_count": len(family_best_aggregates),
572
  "dataset_result_row_count": len(dataset_rows),
 
 
 
 
 
 
 
 
 
 
573
  "source_report_file_count": len(report_files),
574
  "missing_source_report_count": missing_report_count,
575
  "excluded_datasets": dataset_scope["meta"].get("excluded_dataset_keys", []),
 
74
  return number
75
 
76
 
77
+ def _timing_stats_from_report(rows: Any) -> dict[str, Any]:
78
+ """Return a column mean or an explicit whole-dataset fallback."""
79
+ import pandas as pd
80
+
81
+ if rows is None or rows.empty:
82
+ return {"compression": None, "decompression": None, "column_count": 0,
83
+ "aggregation": "unavailable"}
84
+ work = rows.copy()
85
+ if work.get("_timing_source_type") is None:
86
+ work["_timing_source_type"] = "single_report"
87
+ column_groups = []
88
+ aggregate_groups = []
89
+ for source_type, source_rows in work.groupby("_timing_source_type", sort=True):
90
+ columns = source_rows[pd.to_numeric(source_rows["column_index"], errors="coerce") >= 0]
91
+ if not columns.empty:
92
+ grouped = columns.groupby("column_index", as_index=False).agg(
93
+ originalSize=("originalSize", "sum"),
94
+ compressionTime=("compressionTime", "sum"),
95
+ decompressionTime=("decompressionTime", "sum"),
96
+ )
97
+ grouped["_timing_source_type"] = source_type
98
+ column_groups.append(grouped)
99
+ else:
100
+ aggregate = source_rows[pd.to_numeric(source_rows["column_index"], errors="coerce") < 0]
101
+ if not aggregate.empty:
102
+ aggregate_groups.append(aggregate)
103
+ if column_groups and not aggregate_groups:
104
+ grouped = pd.concat(column_groups, ignore_index=True)
105
+ points = grouped["originalSize"] / 8.0
106
+ return {"compression": float((grouped["compressionTime"] / points).mean()),
107
+ "decompression": float((grouped["decompressionTime"] / points).mean()),
108
+ "column_count": int(len(grouped)), "aggregation": "column_mean"}
109
+ elapsed_parts = [*column_groups, *aggregate_groups]
110
+ if elapsed_parts:
111
+ # Use real columns for a typed report when present; use its -1 aggregate
112
+ # only when that typed report lacks column rows, avoiding double count.
113
+ elapsed = pd.concat(elapsed_parts, ignore_index=True)
114
+ points = float(elapsed["originalSize"].sum() / 8.0)
115
+ if points > 0:
116
+ return {"compression": float(elapsed["compressionTime"].sum()) / points,
117
+ "decompression": float(elapsed["decompressionTime"].sum()) / points,
118
+ "column_count": 0, "aggregation": "dataset_aggregate"}
119
+ return {"compression": None, "decompression": None, "column_count": 0,
120
+ "aggregation": "unavailable"}
121
+
122
+
123
  @lru_cache(maxsize=None)
124
  def _algorithm_metadata(algorithm_key: str) -> tuple[bool, str | None, bool]:
125
  from myapp.home_leaderboard_boxplot_data import (
 
279
  compressed_bytes = _finite(record.compressedSize, positive=True)
280
  points = original_bytes / 8.0
281
  timing_rows = work[
282
+ work["algorithm"].astype(str) == str(record.algorithm)
 
283
  ].copy()
284
+ timing = _timing_stats_from_report(timing_rows)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
  rows.append(
286
  {
287
  "dataset": dataset_key,
 
297
  "compression_time_ns": _finite(record.compressionTime),
298
  "decompression_time_ns": _finite(record.decompressionTime),
299
  "compression_rate": compressed_bytes / original_bytes,
300
+ "compression_time_ns_per_point": timing["compression"],
301
+ "decompression_time_ns_per_point": timing["decompression"],
302
+ "timing_column_count": timing["column_count"],
303
+ "timing_aggregation": timing["aggregation"],
304
  }
305
  )
306
  return sorted(rows, key=lambda row: (row["method"], row["algorithm_variant"]))
 
382
  elapsed_point_sum = sum(row["point_count"] for row in elapsed_rows)
383
  comp_time_sum = sum(row["compression_time_ns"] for row in elapsed_rows)
384
  decomp_time_sum = sum(row["decompression_time_ns"] for row in elapsed_rows)
385
+ elapsed_complete = len(elapsed_rows) == len(rows)
386
+ timing_column_count = sum(row.get("timing_column_count", 0) for row in rows)
387
+ modes = [row.get("timing_aggregation", "unavailable") for row in rows]
388
+ valid_times = all(
389
+ row.get("compression_time_ns_per_point") is not None
390
  and row.get("decompression_time_ns_per_point") is not None
391
+ for row in rows
 
 
 
 
 
 
 
 
 
 
 
392
  )
393
+ if not valid_times or "unavailable" in modes:
394
+ average_compression_time = average_decompression_time = None
395
+ timing_aggregation = "unavailable"
396
+ elif "dataset_aggregate" in modes:
397
+ average_compression_time = sum(row["compression_time_ns_per_point"] for row in rows) / len(rows)
398
+ average_decompression_time = sum(row["decompression_time_ns_per_point"] for row in rows) / len(rows)
399
+ timing_aggregation = "dataset_mean"
400
+ elif timing_column_count:
401
+ average_compression_time = sum(
402
+ row["compression_time_ns_per_point"] * row["timing_column_count"] for row in rows
403
+ ) / timing_column_count
404
+ average_decompression_time = sum(
405
+ row["decompression_time_ns_per_point"] * row["timing_column_count"] for row in rows
406
+ ) / timing_column_count
407
+ timing_aggregation = "column_mean"
408
+ else:
409
+ average_compression_time = average_decompression_time = None
410
+ timing_aggregation = "unavailable"
411
  return {
412
  "runtime": runtime,
413
  "scope": scope_key,
 
418
  "overall_compression_rate": compressed_sum / original_sum,
419
  "average_compression_rate": sum(row["compression_rate"] for row in rows) / len(rows),
420
  "weighted_compression_time_ns_per_point": (
421
+ comp_time_sum / elapsed_point_sum if elapsed_point_sum and elapsed_complete else None
422
  ),
423
  "average_compression_time_ns_per_point": average_compression_time,
424
  "weighted_decompression_time_ns_per_point": (
425
+ decomp_time_sum / elapsed_point_sum if elapsed_point_sum and elapsed_complete else None
426
  ),
427
  "average_decompression_time_ns_per_point": average_decompression_time,
428
  "timing_column_count": timing_column_count,
429
+ "timing_aggregation": timing_aggregation,
430
+ "timing_dataset_count": len(rows),
431
  "original_size_bytes": original_sum,
432
  "compressed_size_bytes": compressed_sum,
433
  "point_count": point_sum,
 
562
  "current_result_selection": "visible configurations of methods categorized as lossless-design by the project; result-level float round trips are not established",
563
  "paper_result_selection": "the separate paper_table3 section contains its frozen 20-method C++ snapshot",
564
  "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.",
565
+ "aggregation_note": "Dataset rows with column evidence use an arithmetic mean of per-column nanoseconds per point. Rows backed only by whole-report timing use source dataset elapsed time divided by source point totals and retain a zero column count. If any selected row uses that fallback, method timing is an equal-dataset arithmetic mean; otherwise it is pooled equally over measured columns.",
566
  "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.",
567
  "notes": [
568
  "Missing combinations are unavailable rather than zero.",
 
573
  "compression_rate": "compressed bytes divided by original numeric bytes; lower is better",
574
  "average_compression_rate": "arithmetic mean of per-dataset compression rates",
575
  "overall_compression_rate": "sum of compressed bytes divided by sum of original numeric bytes",
576
+ "average_time_ns_per_point": "column-backed rows use an arithmetic mean of per-column nanoseconds per numeric point; whole-report-only rows use source elapsed nanoseconds divided by source point totals; legacy CSV rows retain their 8-byte-equivalent point denominator",
577
  "weighted_time_ns_per_point": "sum of measured nanoseconds divided by sum of 8-byte numeric points",
578
  "paper_median_balanced_score": "stored paper snapshot value; normalization is paper-scope specific and lower is better",
579
  },
 
582
  "No round-trip verification claim is exported from timing/result CSVs alone.",
583
  "Family-level aggregates select the measured variant with the lowest compression rate within each dataset and method family, matching the backend family-selection rule.",
584
  "dataset_results contains every visible measured configuration; is_best_rate_in_family marks the configuration used for family-level aggregation.",
585
+ "Whole-report timing rows (column_index=-1) remain dataset-level timing evidence with timing_column_count=0; they are never treated as columns.",
586
  "Paper Table 3 values and current report recomputations are separate result sets because the report tree has evolved since the paper snapshot was frozen.",
587
  ],
588
  },
 
606
  "configuration_aggregate_row_count": len(configuration_aggregates),
607
  "family_best_aggregate_row_count": len(family_best_aggregates),
608
  "dataset_result_row_count": len(dataset_rows),
609
+ "timing_column_observation_count": sum(row["timing_column_count"] for row in dataset_rows),
610
+ "timing_column_mean_dataset_result_count": sum(
611
+ row["timing_aggregation"] == "column_mean" for row in dataset_rows
612
+ ),
613
+ "timing_dataset_aggregate_result_count": sum(
614
+ row["timing_aggregation"] == "dataset_aggregate" for row in dataset_rows
615
+ ),
616
+ "timing_unavailable_dataset_result_count": sum(
617
+ row["timing_aggregation"] == "unavailable" for row in dataset_rows
618
+ ),
619
  "source_report_file_count": len(report_files),
620
  "missing_source_report_count": missing_report_count,
621
  "excluded_datasets": dataset_scope["meta"].get("excluded_dataset_keys", []),
index.html CHANGED
@@ -7,7 +7,7 @@
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>
@@ -62,7 +62,7 @@
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>
@@ -108,7 +108,7 @@
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>
 
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-dataset-fallback-v1" type="module"></script>
11
  </head>
12
  <body>
13
  <a class="skip-link" href="#content">Skip to results</a>
 
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 use equal-column arithmetic means where column timings are available. Methods with aggregate-only timings use equal-dataset arithmetic means for the selected range.</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>
 
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 when every selected dataset has column timings. If any selected dataset/configuration has only aggregate timing, use the arithmetic mean of the selected datasets' ns/point values instead, giving each dataset equal weight. For an aggregate-only dataset, ns/point comes from its recorded total elapsed time divided by its recorded point count. Truly missing measurements remain unavailable. Method details and CSV identify the timing basis. 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>
manifest.json CHANGED
@@ -3,7 +3,7 @@
3
  "catalog_dataset_count": 169,
4
  "configuration_aggregate_row_count": 4653,
5
  "dataset_count": 169,
6
- "dataset_result_row_count": 65219,
7
  "excluded_datasets": [
8
  "Arade",
9
  "poisson_timestamps",
@@ -26,12 +26,13 @@
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",
@@ -207,6 +208,64 @@
207
  "source_scope_sha256": "0fabc01f25d62ddb49585b3208103543b16c6e50340195360a0516411fa148b4",
208
  "source_sha256": "febe0d4494cf3778bb3e386ac8674f24406e29d1d7d9b47bd834b332b0e201fb",
209
  "verified_column_count_per_runtime": 4096
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
  }
211
  ],
212
  "result_csv_files": [
@@ -767,14 +826,15 @@
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
  }
 
3
  "catalog_dataset_count": 169,
4
  "configuration_aggregate_row_count": 4653,
5
  "dataset_count": 169,
6
+ "dataset_result_row_count": 65223,
7
  "excluded_datasets": [
8
  "Arade",
9
  "poisson_timestamps",
 
26
  ],
27
  "scope_count": 5,
28
  "source_report_file_count": 108,
29
+ "timing_column_mean_dataset_result_count": 64771,
30
+ "timing_column_observation_count": 1435347,
31
+ "timing_dataset_aggregate_result_count": 452,
32
+ "timing_unavailable_dataset_result_count": 0
33
  },
34
  "data_file": "data.json",
35
+ "data_sha256": "727bf6a91dbb2bc8f63dd87122b8dc1db261589e4da742f28f0e52dad0616571",
36
  "dataset_catalog_sha256": "d69abc85742dc050e4cdab1e5a03fd3392a0e0f1964b2a88ca87ba7f768b7385",
37
  "excluded_content": [
38
  "raw datasets",
 
208
  "source_scope_sha256": "0fabc01f25d62ddb49585b3208103543b16c6e50340195360a0516411fa148b4",
209
  "source_sha256": "febe0d4494cf3778bb3e386ac8674f24406e29d1d7d9b47bd834b332b0e201fb",
210
  "verified_column_count_per_runtime": 4096
211
+ },
212
+ {
213
+ "benchmark_contract": {
214
+ "block_state": "reset for every frozen logical block",
215
+ "codec": "project delta + ZigZag + ULEB128 int64 wire",
216
+ "column_count": 1,
217
+ "cpu_affinity": [
218
+ 37
219
+ ],
220
+ "governor": "schedutil",
221
+ "logical_blocks": 10,
222
+ "method": "DELTA_BINARY_PACKED",
223
+ "repetitions": 1,
224
+ "shared_server": true,
225
+ "timing": "encode and decode timed per block; block nanoseconds summed",
226
+ "timing_aggregation": "column_mean",
227
+ "warmup": "first min(20, logical_block_count) blocks once, metrics discarded",
228
+ "warmup_blocks_executed": 10
229
+ },
230
+ "cross_runtime_golden": {
231
+ "all_runtime_compressed_size_bytes": 615712,
232
+ "all_runtime_decoded_i64le_sha256": "17bd25237309c72cad7b34fa255fd508c5c4b8ce79423e79438d5a8212a68c8b",
233
+ "all_runtime_payload_sha256": "39214295d7739f2417d73b4cc9bb8771a228268500f3dab5a03eacdf7b4aea23",
234
+ "historical_java_compressed_size_bytes": 615712,
235
+ "status": "passed"
236
+ },
237
+ "dataset": "medium_posts",
238
+ "environment": {
239
+ "cpu37_no_user_process_before_after": true,
240
+ "cpu37_processes_after_sha256": "7fcee621228a258ad0262e0bd4c7c3eb8f2452d685982e520a52a2b41cc01816",
241
+ "cpu37_processes_before_sha256": "7fcee621228a258ad0262e0bd4c7c3eb8f2452d685982e520a52a2b41cc01816",
242
+ "cpu_model": "AMD EPYC 7542 32-Core Processor",
243
+ "gxx": "g++ (Ubuntu 11.4.0-1ubuntu1~22.04.2) 11.4.0",
244
+ "hostname": "server",
245
+ "java": "openjdk version \"17.0.16\" 2025-07-15",
246
+ "kernel": "5.15.0-152-generic",
247
+ "machine": "x86_64",
248
+ "python": "3.10.12"
249
+ },
250
+ "family": "DBP",
251
+ "integrated_at": "2026-10-09T07:25:05.180457+00:00",
252
+ "integration_payload_path": "analysis_outputs/column_mean_20261009/dbp_medium_posts_retest/integration_payload.json",
253
+ "integration_payload_sha256": "e45170a955d1cd0df50dba25f9e41f24f00fc8b559e702d59188b6cfa60c99dc",
254
+ "measurement_receipt_path": "analysis_outputs/column_mean_20261009/dbp_medium_posts_retest/measurement_receipt.json",
255
+ "method": "DELTA_BINARY_PACKED",
256
+ "runtimes": [
257
+ "cpp",
258
+ "java",
259
+ "python"
260
+ ],
261
+ "scopes": [
262
+ "integer",
263
+ "overall"
264
+ ],
265
+ "source_sha256": "10359f6f1153655710f7ce9b59d04e5460babe5606487b7794a47ba874ef4324",
266
+ "timing_aggregation": "column_mean",
267
+ "timing_column_count_per_row": 1,
268
+ "validation_status": "passed_byte_for_byte_lossless"
269
  }
270
  ],
271
  "result_csv_files": [
 
826
  }
827
  ],
828
  "timing_recalculation": {
829
+ "aggregate_only_dataset_fallback_row_count": 452,
830
  "dbp_float_audit_path": "analysis_outputs/dbp_float_e2e_20261009/publish_payload/dbp-audit.json",
831
  "dbp_float_audit_sha256": "febe0d4494cf3778bb3e386ac8674f24406e29d1d7d9b47bd834b332b0e201fb",
832
  "dbp_precision_source_file_count": 376,
833
  "dbp_precision_source_hash_status": "all_match",
834
+ "generated_at": "2026-10-09T07:25:05.180457+00:00",
835
+ "policy": "column_mean_with_dataset_aggregate_fallback",
836
  "result_csv_file_count": 108,
837
+ "result_csv_sha256_status": "all_match",
838
+ "timing_unavailable_row_count": 0
839
  }
840
  }
ui-data.json CHANGED
The diff for this file is too large to render. See raw diff