Update C++ API documentation
Hi all,
I've been exploring the C++ API a bit recently, and have found it a bit hard going as there's not much documentation (and my C++ needs more time in the dojo).
There is a great little example here already though, on how to use the ring interface: http://ledatelescope.github.io/bifrost/Cpp-Development.html
So I thought we could add a few more examples?
Here's an example code that shows how to use bifrost arrays in C++. The code compiles and does what it's supposed to do, but is simple (on purpose, but if there's 'better' ways lemme know).
I have some questions in the comments, would be good if someone could help fill these in!
(PS: It would also be good if we had these in an examples directory/repo, that the user can compile)
#include <bifrost/array.h>
#include <bifrost/common.h>
#include <bifrost/ring.h>
#include <utils.hpp>
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
// Simple 'kernel' to add two arrays together, into a third array
BFstatus AddStuff(BFarray *xdata, BFarray *ydata, BFarray *sumdata)
{
long nelements = num_contiguous_elements(xdata);
float* x = (float *)xdata->data;
float* y = (float *)ydata->data;
float* summed = (float *)sumdata->data;
for(int i=0; i < nelements; i +=1)
{
summed[i] = x[i] + y[i];
}
return BF_STATUS_SUCCESS;
}
int main() {
// Create input and output arrays
BFarray bf_data_x;
BFarray bf_data_y;
BFarray bf_data_sum;
int N = 16; // 16 samples in array
// dtype codes are set in array.h
// https://github.com/ledatelescope/bifrost/blob/master/src/bifrost/array.h#L42
// space codes are set in memory.h
// https://github.com/ledatelescope/bifrost/blob/a57a83f8bffc91feed5146d7264a72e0c8ddeb6d/src/bifrost/memory.h#L43
bf_data_x.dtype = BF_DTYPE_I32;
bf_data_x.space = BF_SPACE_SYSTEM;
bf_data_x.ndim = 1;
bf_data_x.shape[0] = N;
//BFstatus codes are set in https://github.com/ledatelescope/bifrost/blob/master/src/bifrost/common.h#L54
//BF_CHECK checks codes, the error codes are found in assert.hpp
//https://github.com/ledatelescope/bifrost/blob/master/src/assert.hpp#L146
BF_CHECK(bfArrayMalloc(&bf_data_x));
// Fill with test vector
int* data_x = (int *)bf_data_x.data;
for(int i=0; i < N; i++) {
data_x[i] = i;
}
// Repeat for Y data arrray
bf_data_y.dtype = BF_DTYPE_I32;
bf_data_y.space = BF_SPACE_SYSTEM;
bf_data_y.ndim = 1;
bf_data_y.shape[0] = N;
BF_CHECK(bfArrayMalloc(&bf_data_y));
// Fill with test vector - set all to 1
int* data_y = (int *)bf_data_y.data;
for(int i=0; i < N; i++) {
data_y[i] = 1;
}
// Repeat for Y data arrray
bf_data_sum.dtype = BF_DTYPE_I32;
bf_data_sum.space = BF_SPACE_SYSTEM;
bf_data_sum.ndim = 1;
bf_data_sum.shape[0] = N;
BF_CHECK(bfArrayMalloc(&bf_data_sum));
int* data_sum = (int *)bf_data_sum.data;
// run the AddStuff function
BF_CHECK(AddStuff(&bf_data_x, &bf_data_y, &bf_data_sum));
// Print some stuff to screen
for(int i=0; i < N; i++) {
cout << "idx[" << i << "]: " << data_x[i] << " + " << data_y[i] << " = " << data_sum[i] << endl;
// Is this the right way to use BF_ASSERT
BF_ASSERT( data_x[i] + data_y[i] == data_sum[i], BF_STATUS_INTERNAL_ERROR);
}
// Free memory
BF_CHECK(bfArrayFree(&bf_data_x));
BF_CHECK(bfArrayFree(&bf_data_y));
BF_CHECK(bfArrayFree(&bf_data_sum));
// TODO: what does bfArrayMemset do? (Show in example)
// TODO: Show bfArrayCopy in another example
// TODO: show dtype2ctype_string from array_util
// TODO: What's the deal with check() in Common.hpp?
// https://github.com/ledatelescope/bifrost/blob/a57a83f8bffc91feed5146d7264a72e0c8ddeb6d/src/bifrost/Common.hpp#L44
// TODO: What's the deal with _check in the python wrappers?
// Should I be using this in my Python plugins?
// Note: Am I using BF_CHECK correctly?
// Note: looks like BF_ASSERT is more common than BF_CHECK? What's the difference?
// https://github.com/ledatelescope/bifrost/blob/master/src/assert.hpp#L102
// TODO: Another tutorial on error handling
// TODO: example with CUDA in it
// TODO: What's the deal with ArrayIndexer.cuh and ShapeIndexer.cuh?
}
I think having more examples, particularly on the C++ side, is a great idea. I'll see if I can find some time to dig into you questions.
// TODO: What's the deal with check() in Common.hpp?
I'm not sure. It looks to be similar to the BF_CHECK macro except that it raises an exception on an error rather than just printing a message and passing the return value along.
// TODO: What's the deal with _check in the python wrappers? // Should I be using this in my Python plugins?
_check in Python either catches some particular kinds of non-BF_STATUS_SUCCESS return values and converts them to Python exceptions. The exact behavior is determined by whether or not the Python constant __debug__ is True or False.
// Note: Am I using BF_CHECK correctly?
Looks like it. As an alternative you could also use the more C-like:
if( !bfArrayMalloc(&bf_data_y) ) {
printf("bfArrayMalloc failed\n");
}
since BF_STATUS_SUCCESS is zero.
// Note: looks like BF_ASSERT is more common than BF_CHECK? What's the difference?
BF_ASSERT throws exceptions in from inside a class method. I think of it as a part of the input validate step in a method, similar to what you would do in Python:
def my_op(input):
assert(input.shape == 2)
input[:,0] += 1
return input
vs.
void my_op(BFarray* input) {
BF_ASSERT(input->ndim == 2, BF_STATUS_INVALID_SHAPE);
for(int i=0; i<input->shape[0]; i++) {
input->data[i*input->shape[1]] += 1;
}
}
(I hope that's the correct syntax there).
BF_CHECK is meant more for check the return code of a method. My Python example here would be it's like checking the return code on scipy.optimize.leastsq.