Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@

## Changes in dev
1. Implement new data types in `RNtuple`
- reduced float types kFloat16, kReal32Trunc, kReal32Quant
- `std::vector`
- `std::map`, `std::unordered_map`, `std::multimap`, `std::unordered_multimap` with `std::pair`
- `std::set`, `std::unordered_set`, `std::multiset`, `std::unordered_multiset`
- `std::array`
- `std::variant`
- `std::tuple`
- kFloat16, kReal32Trunc, kReal32Quant
- `std::bitset`
- `std::atomic`
- simple custom classes
1. Resort order of ranges in http request, fixing several long-standing problems #374
1. Implement for `TPie` 3d, text, title drawing including interactivity
1. Implement `TCanvas` support in `build3d` function #373
Expand Down
30 changes: 30 additions & 0 deletions demo/node/rntuple_test.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@
#include <tuple>
#include <map>
#include <set>
#include <bitset>

class TestClass {
public:
std::string fName;
std::string fTitle;
double fValue{0.};

};

#ifdef __ROOTCLING__
#pragma link C++ class std::map<std::string,float>+;
Expand All @@ -21,6 +30,9 @@
#pragma link C++ class std::multiset<std::string>+;
#pragma link C++ class std::variant<std::string,int,bool>+;
#pragma link C++ class std::tuple<std::string,int,bool>+;
#pragma link C++ class std::bitset<25>+;
#pragma link C++ class std::bitset<117>+;
#pragma link C++ class TestClass+;
#endif


Expand Down Expand Up @@ -55,6 +67,10 @@ void rntuple_test()
auto VariantField = model->MakeField<std::variant<std::string,int,bool>>("VariantField");
auto TupleField = model->MakeField<std::tuple<std::string,int,bool>>("TupleField");
auto ArrayInt = model->MakeField<std::array<int,5>>("ArrayInt");
auto BitsetField = model->MakeField<std::bitset<25>>("BitsetField");
auto LargeBitsetField = model->MakeField<std::bitset<117>>("LargeBitsetField");
auto AtomicDoubleField = model->MakeField<std::atomic<double>>("AtomicDoubleField");
auto TestClassField = model->MakeField<TestClass>("TestClassField");
auto VectString = model->MakeField<std::vector<std::string>>("VectString");
auto VectInt = model->MakeField<std::vector<int>>("VectInt");
auto VectBool = model->MakeField<std::vector<bool>>("VectBool");
Expand Down Expand Up @@ -90,6 +106,10 @@ void rntuple_test()

*TupleField = { std::string("tuple_") + std::to_string(i), i * 3, (i % 3 == 1) };

TestClassField->fName = "name_" + std::to_string(i);
TestClassField->fTitle = "title_" + std::to_string(i);
TestClassField->fValue = i;

VectString->clear();
VectInt->clear();
VectBool->clear();
Expand All @@ -103,6 +123,16 @@ void rntuple_test()
Vect2Float->clear();
Vect2Bool->clear();

BitsetField->reset();
LargeBitsetField->reset();

BitsetField->set(i * 3 % 25, true);

LargeBitsetField->set((i + 7) % 117, true);
LargeBitsetField->set((i + 35) % 117, true);

*AtomicDoubleField = 111.444 * i;

int npx = (i + 5) % 7;
for (int j = 0; j < npx; ++j) {
VectString->emplace_back("str_" + std::to_string(j));
Expand Down
77 changes: 44 additions & 33 deletions demo/node/rntuple_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,73 +5,79 @@ import { readHeaderFooter, rntupleProcess } from 'jsroot/rntuple';
console.log(`JSROOT version ${version}`);


let filename = './rntuple_test.root';
let filename = './rntuple_test.root',
any_error = false;

if (process?.argv && process.argv[2])
filename = process.argv[2];

const file = await openFile(filename),
rntuple = await file.readObject('Data');

await readHeaderFooter(rntuple);
rntuple = await file.readObject('Data'),
builder = await readHeaderFooter(rntuple);

console.log('Performing Validations and debugging info');

if (rntuple.builder?.name !== 'Data')
if (builder?.name !== 'Data')
console.error('FAILURE: name differs from expected');
else
console.log('OK: name is', rntuple.builder?.name);
console.log('OK: name is', builder?.name);


if (rntuple.builder?.description !== '')
if (builder.description !== '')
console.error('FAILURE: description should be the empty string');
else
console.log('OK: description is empty string');

if (rntuple.builder?.xxhash3 === undefined || rntuple.builder.xxhash3 === null)
if (builder.xxhash3 === undefined || builder.xxhash3 === null)
console.warn('WARNING: xxhash3 is missing');
else
console.log('OK: xxhash3 is', '0x' + rntuple.builder.xxhash3.toString(16).padStart(16, '0'));
console.log('OK: xxhash3 is', '0x' + builder.xxhash3.toString(16).padStart(16, '0'));

// Fields Check

if (!rntuple.builder?.fieldDescriptors?.length)
if (!builder?.fieldDescriptors?.length)
console.error('FAILURE: No fields deserialized');
else {
console.log(`OK: ${rntuple.builder.fieldDescriptors.length} field(s) deserialized`);
for (let i = 0; i < rntuple.builder.fieldDescriptors.length; ++i) {
const field = rntuple.builder.fieldDescriptors[i];
console.log(`OK: ${builder.fieldDescriptors.length} field(s) deserialized`);
for (let i = 0; i < builder.fieldDescriptors.length; ++i) {
const field = builder.fieldDescriptors[i];
if (!field.fieldName || !field.typeName)
console.error(`FAILURE: Field ${i} is missing name or type`);
else
console.log(`OK: Field ${i}: ${field.fieldName} (${field.typeName})`);
if (i === 0) {
if (field.fieldName !== 'IntField' || field.typeName !== 'std::int32_t')
if (field.fieldName !== 'IntField' || field.typeName !== 'std::int32_t') {
any_error = true;
console.error(`FAILURE: First field should be 'IntField (std::int32_t)' but got '${field.fieldName} (${field.typeName})'`);
} else if (i === rntuple.builder.fieldDescriptors.length - 1) {
if (field.fieldName !== '_1' || field.typeName !== 'bool')
}
} else if (i === builder.fieldDescriptors.length - 1) {
if (field.fieldName !== '_1' || field.typeName !== 'bool') {
any_error = true;
console.error(`FAILURE: Last field should be '_1 (bool)' but got '${field.fieldName} (${field.typeName})'`);
}
}
}
}

// Column Check

if (!rntuple.builder?.columnDescriptors?.length)
if (!rntuple.builder?.columnDescriptors?.length) {
any_error = true;
console.error('FAILURE: No columns deserialized');
else {
console.log(`OK: ${rntuple.builder.columnDescriptors.length} column(s) deserialized`);
for (let i = 0; i < rntuple.builder.columnDescriptors.length; ++i) {
const column = rntuple.builder.columnDescriptors[i];
} else {
console.log(`OK: ${builder.columnDescriptors.length} column(s) deserialized`);
for (let i = 0; i < builder.columnDescriptors.length; ++i) {
const column = builder.columnDescriptors[i];
if (column.fieldId === undefined || column.fieldId === null)
console.error(`FAILURE: Column ${i} is missing fieldId`);
else
console.log(`OK: Column ${i} fieldId: ${column.fieldId} `);
if (i === 0) {
if (column.fieldId !== 0)
console.error('FAILURE: First column should be for fieldId 0');
} else if (i === rntuple.builder.columnDescriptors.length - 1) {
if (column.fieldId !== 38)
console.error('FAILURE: Last column should be for fieldId 38');
if ((i === 0) && (column.fieldId !== 0)) {
any_error = true;
console.error('FAILURE: First column should be for fieldId 0');
} else if ((i === builder.columnDescriptors.length - 1) && (column.fieldId !== builder.fieldDescriptors.length - 1)) {
any_error = true;
console.error(`FAILURE: Last column should be for fieldId ${builder.fieldDescriptors.length - 1}`);
}
}
}
Expand All @@ -81,9 +87,10 @@ const selector = new TSelector(),
fields = ['IntField', 'FloatField', 'DoubleField',
'Float16Field', 'Real32Trunc', 'Real32Quant',
'StringField', 'BoolField',
'ArrayInt', 'VariantField', 'TupleField',
'ArrayInt', 'BitsetField', 'LargeBitsetField',
'AtomicDoubleField', 'TestClassField', '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)
Expand All @@ -97,8 +104,6 @@ selector.Begin = () => {
// Now validate entry data
const EPSILON = 1e-7;

let any_error = false;

function compare(expected, value, eps) {
if (typeof expected === 'number')
return Math.abs(value - expected) < (eps ?? EPSILON);
Expand Down Expand Up @@ -134,6 +139,10 @@ selector.Process = function(entryIndex) {
StringField: `entry_${entryIndex}`,
BoolField: entryIndex % 3 === 1,
ArrayInt: [entryIndex + 1, entryIndex + 2, entryIndex + 3, entryIndex + 4, entryIndex + 5],
BitsetField: 1 << entryIndex * 3 % 25,
LargeBitsetField: (1n << BigInt((entryIndex + 7) % 117)) | (1n << BigInt((entryIndex + 35) % 117)),
AtomicDoubleField: entryIndex * 111.444,
TestClassField: { _typename: 'TestClass', fName: `name_${entryIndex}`, fTitle: `title_${entryIndex}`, fValue: entryIndex },
VariantField: null,
TupleField: { _0: `tuple_${entryIndex}`, _1: entryIndex*3, _2: (entryIndex % 3 === 1) },
VectString: [],
Expand Down Expand Up @@ -183,8 +192,10 @@ selector.Process = function(entryIndex) {
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
console.log(`OK: ${field} at entry ${entryIndex} = ${JSON.stringify(value)}`);
} else {
const res = typeof value === 'bigint' ? value.toString() + 'n' : JSON.stringify(value);
console.log(`OK: ${field} at entry ${entryIndex} = ${res}`);
}
} catch (err) {
console.error(`ERROR: Failed to read ${field} at entry ${entryIndex}: ${err.message}`);
any_error = true;
Expand Down
Binary file modified demo/node/rntuple_test.root
Binary file not shown.
6 changes: 5 additions & 1 deletion modules/gui/HierarchyPainter.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,11 @@ function objectHierarchy(top, obj, args = undefined) {
}
}
}
} else if ((typeof fld === 'number') || (typeof fld === 'boolean') || (typeof fld === 'bigint')) {
} else if (typeof fld === 'bigint') {
simple = true;
item._value = fld.toString() + 'n';
item._vclass = cssValueNum;
} else if ((typeof fld === 'number') || (typeof fld === 'boolean')) {
simple = true;
if (key === 'fBits')
item._value = '0x' + fld.toString(16);
Expand Down
77 changes: 76 additions & 1 deletion modules/rntuple.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1132,6 +1132,42 @@ class ArrayReaderItem extends ReaderItem {
}


/** @class reading of std::bitset<N>
* @desc large numbers with more than 48 bits converted to BigInt
* @private */

class BitsetReaderItem extends ReaderItem {

constructor(items, tgtname, size) {
super(items, tgtname);
this.size = size;
items[0].set_not_simple();
this.bigint = size > 48;
}

func(tgtobj) {
const tmp = {};
let len = 0, res = this.bigint ? 0n : 0;
while (len < this.size) {
this.items[0].func(tmp);
if (tmp.bit) {
if (this.bigint)
res |= (1n << BigInt(len));
else
res |= 1 << len;
}
len++;
}
tgtobj[this.name] = res;
}

shift(entries) {
this.items[0].shift(entries * this.size);
}

}


/** @class reading std::vector and other kinds of collections
* @private */

Expand Down Expand Up @@ -1179,7 +1215,6 @@ class VariantReaderItem extends ReaderItem {

constructor(items, tgtname) {
super(items, tgtname);
this.items = items;
this.set_not_simple();
}

Expand Down Expand Up @@ -1213,6 +1248,29 @@ class TupleReaderItem extends ReaderItem {

}

/** @class reading custom class field
* @private */

class CustomClassReaderItem extends ReaderItem {

constructor(items, tgtname, classname) {
super(items, tgtname);
this.classname = classname;
this.set_not_simple();
}

func(tgtobj) {
const obj = { _typename: this.classname };
this.items.forEach(item => item.func(obj));
tgtobj[this.name] = obj;
}

shift(entries) {
this.items.forEach(item => item.shift(entries));
}

}


/** @class reading std::pair field
* @private */
Expand Down Expand Up @@ -1354,13 +1412,24 @@ async function rntupleProcess(rntuple, selector, args = {}) {
return new ArrayReaderItem([item1], tgtname, Number(field.arraySize));
}

if ((childs.length === 1) && (field.typeName.indexOf('std::atomic') === 0))
return addFieldReading(builder, childs[0], tgtname);


if ((childs.length > 0) && (field.typeName.indexOf('std::tuple') === 0)) {
const items = [];
for (let i = 0; i < childs.length; ++i)
items.push(addFieldReading(builder, childs[i], `_${i}`));
return new TupleReaderItem(items, tgtname);
}

if ((childs.length > 0) && field.checksum && field.typeName) {
const items = [];
for (let i = 0; i < childs.length; ++i)
items.push(addFieldReading(builder, childs[i], childs[i].fieldName));
return new CustomClassReaderItem(items, tgtname, field.typeName);
}

throw new Error(`No columns found for field '${field.fieldName}' in RNTuple`);
}

Expand All @@ -1370,6 +1439,12 @@ async function rntupleProcess(rntuple, selector, args = {}) {
return new StringReaderItem([itemlen, itemstr], tgtname);
}

if ((columns.length === 1) && (field.typeName.indexOf('std::bitset') === 0)) {
const itembit = addColumnReadout(columns[0], 'bit');
return new BitsetReaderItem([itembit], tgtname, Number(field.arraySize));
}


let is_stl = false;
['vector', 'map', 'unordered_map', 'multimap', 'unordered_multimap', 'set', 'unordered_set', 'multiset', 'unordered_multiset'].forEach(name => {
if (field.typeName.indexOf('std::' + name) === 0)
Expand Down