diff --git a/demo/node/rntuple_test.cxx b/demo/node/rntuple_test.cxx index af6cbd78b..cdb3253b0 100644 --- a/demo/node/rntuple_test.cxx +++ b/demo/node/rntuple_test.cxx @@ -40,6 +40,15 @@ void rntuple_test() // shared pointers of the given type auto IntField = model->MakeField("IntField"); auto FloatField = model->MakeField("FloatField"); + auto Float16Field = model->MakeField("Float16Field"); + model->GetMutableField("Float16Field").SetColumnRepresentatives({{ROOT::ENTupleColumnType::kReal16}}); + + auto Real32Trunc = model->MakeField("Real32Trunc"); + dynamic_cast &>(model->GetMutableField("Real32Trunc")).SetTruncated(20); + + auto Real32Quant = model->MakeField("Real32Quant"); + dynamic_cast &>(model->GetMutableField("Real32Quant")).SetQuantized(0., 1., 14); + auto DoubleField = model->MakeField("DoubleField"); auto StringField = model->MakeField("StringField"); auto BoolField = model->MakeField("BoolField"); @@ -56,8 +65,6 @@ void rntuple_test() auto MapIntDouble = model->MakeField>("MapIntDouble"); auto MapStringBool = model->MakeField>("MapStringBool"); - - // We hand-over the data model to a newly created ntuple of name "F", stored in kNTupleFileName // In return, we get a unique pointer to an ntuple that we can fill auto writer = ROOT::RNTupleWriter::Recreate(std::move(model), "Data", kNTupleFileName); @@ -66,6 +73,11 @@ void rntuple_test() *IntField = i; *FloatField = i*i; + + *Float16Field = 0.1987333 * i; // stored as 16 bits float + *Real32Trunc = 123.45 * i; // here only 20 bits preserved + *Real32Quant = 0.03 * (i % 30); // value should be inside [0..1] + *DoubleField = 0.5 * i; *StringField = "entry_" + std::to_string(i); *BoolField = (i % 3 == 1); @@ -112,8 +124,6 @@ void rntuple_test() } Vect2Float->emplace_back(vf); Vect2Bool->emplace_back(vb); - - } writer->Fill(); diff --git a/demo/node/rntuple_test.js b/demo/node/rntuple_test.js index 97bf619dd..fbc05f35f 100644 --- a/demo/node/rntuple_test.js +++ b/demo/node/rntuple_test.js @@ -78,10 +78,14 @@ else { // Setup selector to process all fields (so cluster gets loaded) const selector = new TSelector(), - fields = ['IntField', 'FloatField', 'DoubleField', 'StringField', 'BoolField', + fields = ['IntField', 'FloatField', 'DoubleField', + 'Float16Field', 'Real32Trunc', 'Real32Quant', + 'StringField', 'BoolField', 'ArrayInt', 'VariantField', 'TupleField', 'VectString', 'VectInt', 'VectBool', 'Vect2Float', 'Vect2Bool', 'MultisetField', - 'MapStringFloat', 'MapIntDouble', 'MapStringBool']; + 'MapStringFloat', 'MapIntDouble', 'MapStringBool'], + epsilonValues = { Real32Trunc: 0.3, Real32Quant: 1e-4, Float16Field: 1e-2 }; + for (const f of fields) selector.addBranch(f); @@ -89,14 +93,15 @@ selector.Begin = () => { console.log('\nBegin processing to load cluster data...'); }; + // Now validate entry data const EPSILON = 1e-7; let any_error = false; -function compare(expected, value) { +function compare(expected, value, eps) { if (typeof expected === 'number') - return Math.abs(value - expected) < EPSILON; + return Math.abs(value - expected) < (eps ?? EPSILON); if (typeof expected === 'object') { if (expected.length !== undefined) { if (expected.length !== value.length) @@ -123,6 +128,9 @@ selector.Process = function(entryIndex) { IntField: entryIndex, FloatField: entryIndex * entryIndex, DoubleField: entryIndex * 0.5, + Float16Field: entryIndex * 0.1987333, + Real32Trunc: 123.45 * entryIndex, + Real32Quant: 0.03 * (entryIndex % 30), StringField: `entry_${entryIndex}`, BoolField: entryIndex % 3 === 1, ArrayInt: [entryIndex + 1, entryIndex + 2, entryIndex + 3, entryIndex + 4, entryIndex + 5], @@ -172,7 +180,7 @@ selector.Process = function(entryIndex) { const value = this.tgtobj[field], expected = expectedValues[field]; - if (!compare(expected, value)) { + if (!compare(expected, value, epsilonValues[field])) { console.error(`FAILURE: ${field} at entry ${entryIndex} expected ${JSON.stringify(expected)}, got ${JSON.stringify(value)}`); any_error = true; } else diff --git a/demo/node/rntuple_test.root b/demo/node/rntuple_test.root index 779758184..a610d96d8 100644 Binary files a/demo/node/rntuple_test.root and b/demo/node/rntuple_test.root differ diff --git a/modules/rntuple.mjs b/modules/rntuple.mjs index b634f38ff..aa5fb54e5 100644 --- a/modules/rntuple.mjs +++ b/modules/rntuple.mjs @@ -852,18 +852,59 @@ class ReaderItem { break; case kReal16: this.func = function(obj) { - obj[this.name] = this.view.getUint16(this.o, LITTLE_ENDIAN); + const value = this.view.getUint16(this.o, LITTLE_ENDIAN); this.shift_o(2); + // reimplementing of HalfToFloat + let fbits = (value & 0x8000) << 16, + abs = value & 0x7FFF; + if (abs) { + fbits |= 0x38000000 << (abs >= 0x7C00 ? 1 : 0); + for (; abs < 0x400; abs <<= 1, fbits -= 0x800000); + fbits += abs << 13; + } + this.buf.setUint32(0, fbits, true); + obj[this.name] = this.buf.getFloat32(0, true); }; this.sz = 2; + this.buf = new DataView(new ArrayBuffer(4), 0); break; case kReal32Trunc: case kReal32Quant: + this.nbits = this.column.bitsOnStorage; + if (this.coltype === kReal32Trunc) + this.buf = new DataView(new ArrayBuffer(4), 0); + else { + this.factor = (this.column.maxValue - this.column.minValue) / ((1 << this.nbits) - 1); + this.min = this.column.minValue; + } + this.func = function(obj) { - obj[this.name] = this.view.getUint32(this.o, LITTLE_ENDIAN); - this.shift_o(4); + let res = 0, len = this.nbits; + // extract nbits from the + while (len > 0) { + if (this.o2 === 0) { + this.byte = this.view.getUint8(this.o); + this.o2 = 8; // number of bits in the value + } + const pos = this.nbits - len; // extracted bits + if (len >= this.o2) { + res |= (this.byte & ((1 << this.o2) - 1)) << pos; // get all remaining bits + len -= this.o2; + this.o2 = 0; + this.shift_o(1); + } else { + res |= (this.byte & ((1 << len) - 1)) << pos; // get only len bits from the value + this.o2 -= len; + this.byte >>= len; + len = 0; + } + } + if (this.buf) { + this.buf.setUint32(0, res << (32 - this.nbits), true); + obj[this.name] = this.buf.getFloat32(0, true); + } else + obj[this.name] = res * this.factor + this.min; }; - this.sz = 4; break; case kInt64: case kIndex64: @@ -993,12 +1034,8 @@ class ReaderItem { async unzipBlob(blob, cluster_locations, page_indx) { const colEntry = cluster_locations[this.id], // Access column entry numElements = Number(colEntry.pages[page_indx].numElements), - elementSize = this.column.bitsOnStorage / 8; - - let expectedSize = numElements * elementSize; - // Special handling for boolean fields - if (this.coltype === kBit) - expectedSize = Math.ceil(numElements / 8); + elementSize = this.column.bitsOnStorage / 8, + expectedSize = Math.ceil(numElements * elementSize); // Check if data is compressed if ((colEntry.compression === 0) || (blob.byteLength === expectedSize))