Accessing Numeric Data¶
You can access leaf types (numeric scalars or arrays) using Node’s as_{type} methods. You can let Conduit handle striding with the DataArray class, and additionally type conversion with the DataAccessor class.
Accessing Scalars¶
The call to Node::as_int64() gives you the numeric value. Make sure the Node does actually contain an int64, or Conduit will throw an error.
Node n;
int64 val = 100;
n = val;
std::cout << n.as_int64() << std::endl;
100
Or you can use Node::value(), which can infer the correct return type via a cast. As with the as_{type} methods, make sure to ask for the type of data that the Node actually contains.
Node n;
int64 val = 100;
n = val;
int64 my_val = n.value();
std::cout << my_val << std::endl;
100
Accessing Arrays¶
You can get numeric array data out of Conduit as a pointer; as a DataArray, which handles striding; or as a DataAccessor, which handles striding and type conversion.
Access array data via pointers using Node’s as_{type} methods.
int64 vals[4] = {100,200,300,400};
Node n;
n.set(vals,4);
int64 *my_vals = n.as_int64_ptr();
for(index_t i=0; i < 4; i++)
{
std::cout << "my_vals[" << i << "] = " << my_vals[i] << std::endl;
}
my_vals[0] = 100
my_vals[1] = 200
my_vals[2] = 300
my_vals[3] = 400
Or using Node::value():
int64 vals[4] = {100,200,300,400};
Node n;
n.set(vals,4);
int64 *my_vals = n.value();
for(index_t i=0; i < 4; i++)
{
std::cout << "my_vals[" << i << "] = " << my_vals[i] << std::endl;
}
my_vals[0] = 100
my_vals[1] = 200
my_vals[2] = 300
my_vals[3] = 400
For non-contiguous arrays, direct pointer access is complex due to the indexing required. Conduit provides a simple DataArray class that handles per-element indexing for all types of arrays.
int64 vals[4] = {100,200,300,400};
Node n;
n.set(vals,2, // # of elements
0, // offset in bytes
sizeof(int64)*2); // stride in bytes
int64_array my_vals = n.value();
for(index_t i=0; i < 2; i++)
{
std::cout << "my_vals[" << i << "] = " << my_vals[i] << std::endl;
}
my_vals.print();
my_vals[0] = 100
my_vals[1] = 300
[100, 300]
The DataAccessor class lets you write code to gracefully handle data of varying type, such as user input data. See the next section for an example of DataAccessor use.
Using Introspection and Conversion¶
In this example, we have an array in a node that we are interested in processing using an existing function that only handles doubles. We ensure the node is compatible with the function, or transform it to a contiguous double array.
//-----------------------------------------------------------------------------
void must_have_doubles_function(double *vals,index_t num_vals)
{
for(int i = 0; i < num_vals; i++)
{
std::cout << "vals[" << i << "] = " << vals[i] << std::endl;
}
}
//-----------------------------------------------------------------------------
void process_doubles(Node & n)
{
Node res;
// We have a node that we are interested in processing with
// and existing function that only handles doubles.
if( n.dtype().is_double() && n.dtype().is_compact() )
{
std::cout << " using existing buffer" << std::endl;
// we already have a contiguous double array
res.set_external(n);
}
else
{
std::cout << " converting to temporary double array " << std::endl;
// Create a compact double array with the values of the input.
// Standard casts are used to convert each source element to
// a double in the new array.
n.to_double_array(res);
}
res.print();
double *dbl_vals = res.value();
index_t num_vals = res.dtype().number_of_elements();
must_have_doubles_function(dbl_vals,num_vals);
}
//-----------------------------------------------------------------------------
TEST(conduit_tutorial, numeric_double_conversion)
{
float32 f32_vals[4] = {100.0,200.0,300.0,400.0};
double d_vals[4] = {1000.0,2000.0,3000.0,4000.0};
Node n;
n["float32_vals"].set(f32_vals,4);
n["double_vals"].set(d_vals,4);
std::cout << "float32 case: " << std::endl;
process_doubles(n["float32_vals"]);
std::cout << "double case: " << std::endl;
process_doubles(n["double_vals"]);
}
[ OK ] conduit_tutorial.numeric_double_conversion_start (0 ms)
[ RUN ] conduit_tutorial.numeric_double_conversion
float32 case:
converting to temporary double array
[100.0, 200.0, 300.0, 400.0]
vals[0] = 100
vals[1] = 200
vals[2] = 300
vals[3] = 400
double case:
using existing buffer
[1000.0, 2000.0, 3000.0, 4000.0]
vals[0] = 1000
vals[1] = 2000
vals[2] = 3000
vals[3] = 4000
[ OK ] conduit_tutorial.numeric_double_conversion (0 ms)
[ RUN ] conduit_tutorial.numeric_double_conversion_end
Alternately, we can use a DataAccessor to do the conversion as needed.
conduit_tutorial_cpp_numeric_data_accessor_start
---------------------------------------------------------------------------
takes_float64_accessor(float64_accessor vals)
std::cout << "Some stats:\n\t min: " << vals.min() <<
"\n\t max: " << vals.max() <<
"\n\t sum: " << vals.sum() <<
"\n\t mean: " << vals.mean() <<
std::endl;
for(int i = 0; i < vals.number_of_elements(); i++)
{
std::cout << "vals[" << i << "] = " << vals[i] << std::endl;
}
---------------------------------------------------------------------------
process_doubles_with_accessor(Node & n)
float64_accessor vals = n.value();
vals.print();
takes_float64_accessor(vals);
---------------------------------------------------------------------------
(conduit_tutorial, numeric_data_accessor)
int64 i_vals[4] = {100,200,300,400};
Node n;
n.set(i_vals,2, // # of elements
0, // offset in bytes
sizeof(int64)*2); // stride in bytes
// Process values as float64, even though they're integers
std::cout << "int64 case: " << std::endl;
process_doubles_with_accessor(n);
float64 dvals[6] = {1.1,2.2,3.3,4.4,5.5,6.6};
Node dn;
dn.set(dvals,3, // # of elements
8, // offset in bytes
sizeof(float64)*2); // stride in bytes
// Process values as float64
std::cout << "float64 case: " << std::endl;
process_doubles_with_accessor(dn);
conduit_tutorial_cpp_numeric_data_accessor_end
---------------------------------------------------------------------------
(conduit_tutorial, numeric_data_accessor_end)
[ OK ] conduit_tutorial.numeric_data_accessor_start (0 ms)
[ RUN ] conduit_tutorial.numeric_data_accessor
int64 case:
[100.0, 300.0]
Some stats:
min: 100
max: 300
sum: 400
mean: 200
vals[0] = 100
vals[1] = 300
float64 case:
[2.2, 4.4, 6.6]
Some stats:
min: 2.2
max: 6.6
sum: 13.2
mean: 4.4
vals[0] = 2.2
vals[1] = 4.4
vals[2] = 6.6
[ OK ] conduit_tutorial.numeric_data_accessor (0 ms)
[ RUN ] conduit_tutorial.numeric_data_accessor_end
The first version examines the data type of the Node passed to process_doubles() and converts the entire array if needed. The worker function must_have_doubles_function() works with a pointer, as is the case with much existing code, giving speed of access with no guard-rails. The second version constructs a float64_accessor from its Node argument and passes that to the worker function takes_float64_accessor(). That worker function shows how to use the summary methods of DataAccessor and then prints the values in the array passed to it. The DataAccessor casts each value as needed on access, thus incurring a small cost at each access. The DataAccessor is also safer and simpler to use.
C++11 Initializer Lists¶
You can set Node values using C++11 style initializer lists of numeric literals.
Node n;
// set with integer c++11 initializer list
n.set({100,200,300});
n.print();
// assign with integer c++11 initializer list
n = {100,200,300};
n.print();
// set with floating point c++11 initializer list
n.set({1.0,2.0,3.0});
n.print();
// assign with floating point c++11 initializer list
n = {1.0,2.0,3.0};
n.print();
[100, 200, 300]
[100, 200, 300]
[1.0, 2.0, 3.0]
[1.0, 2.0, 3.0]