About Documentation Tutorial

Tutorial - C++

C++ Tutorial

Initialization
Although initialization of the framework can happen implicitly the shutdown still has to be done explicitly to allow the backends to deallocate memory and shut down threads. This can be done with a single call:
flintCleanup();

Initialization allows to start specific backends. If only one of both is started only this one will be used. If both are started, both will be used (which of both for an execution is determined by heuristics). If you don't start a backend, both will be initialized implicitly upon the first execution.
flintInit(FLINT_BACKEND_ONLY_CPU); // init only cpu backend
flintInit(FLINT_BACKEND_ONLY_GPU); // init only gpu backend
flintInit(FLINT_BACKEND_BOTH); // init both 

Instead of those single calls it is also possible to create a context object that initializes the backends upon creation and cleans them up upon destruction:
FlintContext _(FLINT_BACKEND_ONLY_GPU);
// your Flint code here

You can modify the execution strategy (eager or uneager) and logging mode with:
fEnableEagerExecution();
fDisableEagerExecution();
fSetLoggingLevel(F_WARNING);
fSetLoggingLevel(F_INFO);
fSetLoggingLevel(F_VERBOSE);
The Tensor Class
Flint's central datastructure in the C++ frontend is the Tensor class. The Tensor class binds its rank or dimensionality (the number of dimensions) and the underlying type of its data to its type as templates. You can initialize it with a std::vector or an initializer list:
Tensor<float, 2> t1 = {{0.5f, 0.9f}, {2.3f, 3.1f}};

The data is essentially immutable for a Tensor, meaning if you want to modify it, you need to create a new one:
Tensor<float, 2> t2 = t1 + 1;

You can directly pass a Tensor to a stream, but keep uneager execution on your mind, because then no data will be available. To avoid that you can implicitly execute a Tensor with the call operator:
std::cout << t1 << std::endl;
// Tensor<FLOAT32, shape: [2, 2]>(
// [[0.500000, 0.900000],
//  [2.300000, 3.100000]])
std::cout << t2 << std::endl;
// Tensor<FLOAT32, shape: [2, 2]>(<not yet executed>)
std::cout << t2() << std::endl;
// Tensor<FLOAT32, shape: [2, 2]>(
// [[1.500000, 1.900000],
//  [3.300000, 4.100000]])

But be careful with the execute operator, since it returns a reference to the object itself! Meaning code like
do_something_with_it((t1 + t2)());

could lead to possible memory problems! This can be avoided with explicit calls to .execute() and local variables (so that the Tensor is binded).
You can query elements of a Tensor with the indexing operator, if the Tensor was not executed it will be implicitly:
std::cout << t1[0][1] << std::endl; // 0.9
std::cout << t2[1][1] << std::endl; // 4.1

or you can convert the Tensor completly to a vector with the * operator (of course less efficient then single indexing calls):
std::vector<std::vector<float>> v1 = *t1;

The Tensor class supports move operations (which will be essentially free performance and memory wise) and copy operations, that have to copy the complete graph structure and result data, which are therefore very inefficient (and to be avoided wherever possible).

Generating Data

Most of the times you want to generate your Tensors from constants, random values or load it from files:
Tensor<int, 3> zeros = Flint::constant(0, 3, 2, 3); 
// Tensor<INT32, shape: [3, 2, 3]>(
// [[[0, 0, 0],
//   [0, 0, 0]],
//  [[0, 0, 0],
//   [0, 0, 0]],
//  [[0, 0, 0],
//   [0, 0, 0]]])
Tensor<int, 3> zeros_alt = Flint::constant_array(0, std::array<double, 3>{3, 2, 3}); 
// same as zeros, for cases were variadic templates isn't what you want
Tensor<double, 3> randoms = Flint::random(3, 2, 2); // in [0, 1)
// Tensor<FLOAT64, shape: [3, 2, 2]>(
// [[[0.695820, 0.287424],
//   [0.395240, 0.838207]],
//  [[0.089141, 0.703297],
//   [0.589048, 0.043032]],
//  [[0.565522, 0.398417],
//   [0.365827, 0.633404]]])
Tensor<double, 3> randoms_alt = Flint::random_array(std::array<double, 3>{3, 2, 2});
// same as randoms

You can store and load a Tensor into a binary file:
// store to file
std::ofstream my_store("zeros.flint");
my_store << zeros;
my_store.close();
// load from file
std::ifstream my_load("zeros.flint");
Tensor<double, 3> loaded = Tensor<double, 3>::read_from(my_load);
my_load.close();

and load and store images to and from Tensors:
Tensor<float, 3> img = Flint::load_image("icon.png"); // height, width, channels
// values are between 0 and 1, channels are 4 for rgba, 3 for rgb, 1 for greyscale, ...
Flint::store_image(img, "icon2.png", F_PNG); 
// has to have the data format as third argument