/*! @page bindings mlpack automatic bindings to other languages @section bindings_overview Overview mlpack has a system to automatically generate bindings to other languages, such as Python and command-line programs, and it is extensible to other languages with some amount of ease. The maintenance burden of this system is low, and it is designed in such a way that the bindings produced are always up to date across languages and up to date with the mlpack library itself. This document describes the full functioning of the system, and is a good place to start for someone who wishes to understand the system so that they can contribute a new binding language, or someone who wants to understand so they can adapt the system for use in their own project, or someone who is simply curious enough to see how the sausage is made. @section bindings_intro Introduction C++ is not the most popular language on the planet, and it (unfortunately) can scare many away with its ultra-verbose error messages, confusing template rules, and complex metaprogramming techniques. Most practitioners of machine learning tend to avoid writing native C++ and instead prefer other languages---probably most notably Python. In the case of Python, many projects will use tools like SWIG (http://www.swig.org/) to automatically generate bindings, or they might hand-write Cython. The same types of strategies may be used for other languages; hand-written MEX files may be used for MATLAB, hand-written RCpp bindings might be used for R bindings, and so forth. However, these approaches have a fundamental flaw: the hand-written bindings must be maintained, and risk going out of date as the rest of the library changes or new functionality is added. This incurs a maintenance burden: each major change to the library means that someone must update the bindings and test that they are still working. mlpack is not prepared to handle this maintenance workload; therefore an alternate solution is needed. At the time of the design of this system, mlpack shipped headers for a C++ library as well as many (~40) hand-written command-line programs that used the mlpack::CLI object to manage command-line arguments. These programs all had similar structure, and could be logically split into three sections: - parse the input options supplied by the user - run the machine learning algorithm - prepare the output to return to the user The user might interface with this command-line program like the following: @code $ mlpack_knn -r reference.csv -q query.csv -k 3 -d d.csv -n n.csv @endcode That is, they would pass a number of input options---some were numeric values (like @c -k @c 3 ); some were filenames (like @c -r @c reference.csv ); and a few other types also. Therefore, the first stage of the program---parsing input options---would be handled by reading the command line and loading any input matrices. Preparing the output, which usually consists of data matrices (i.e. @c -d @c d.csv ) involves saving the matrix returned by the algorithm to the user's desired file. Ideally, any binding to any language would have this same structure, and the actual "run the machine learning algorithm" code could be identical. For MATLAB, for instance, we would not need to read the file @c reference.csv but instead the user would simply pass their data matrix as an argument. So each input and output parameter would need to be handled differently, but the algorithm could be run identically across all bindings. Therefore, design of an automatically-generated binding system would simply involve generating the boilerplate code necessary to parse input options for a given language, and to return output options to a user. @section bindings_code Writing code that can be turned into a binding This section details what a binding file might actually look like. It is good to have this API in mind when reading the following sections. Each mlpack binding is typically contained in the @c src/mlpack/methods/ folder corresponding to a given machine learning algorithm, with the suffix @c _main.cpp ; so an example is @c src/mlpack/methods/pca/pca_main.cpp . These files have roughly two parts: - definition of the input and output parameters with @c PARAM macros - implementation of @c mlpackMain(), which is the actual machine learning code Here is a simple example file: @code // This is a stripped version of mean_shift_main.cpp. #include #include #include #include #include "mean_shift.hpp" using namespace mlpack; using namespace mlpack::meanshift; using namespace mlpack::kernel; using namespace std; // Define parameters for the executable. PROGRAM_INFO("Mean Shift Clustering", "This program performs mean shift " "clustering on the given dataset, storing the learned cluster assignments " "either as a column of labels in the file containing the input dataset or " "in a separate file."); // Required option: the user must give us a matrix. PARAM_MATRIX_IN_REQ("input", "Input dataset to perform clustering on.", "i"); // Output options: the user can save the output matrix of labels and/or the // centroids. PARAM_UCOL_OUT("output", "Matrix to write output labels to.", "o"); PARAM_MATRIX_OUT("centroid", "If specified, the centroids of each cluster will " "be written to the given matrix.", "C"); // Mean shift configuration options. PARAM_INT_IN("max_iterations", "Maximum number of iterations before mean shift " "terminates.", "m", 1000); PARAM_DOUBLE_IN("radius", "If the distance between two centroids is less than " "the given radius, one will be removed. A radius of 0 or less means an " "estimate will be calculated and used for the radius.", "r", 0); void mlpackMain() { // Process the parameters that the user passed. const double radius = CLI::GetParam("radius"); const int maxIterations = CLI::GetParam("max_iterations"); if (maxIterations < 0) { Log::Fatal << "Invalid value for maximum iterations (" << maxIterations << ")! Must be greater than or equal to 0." << endl; } // Warn, if the user did not specify that they wanted any output. if (!CLI::HasParam("output") && !CLI::HasParam("centroid")) { Log::Warn << "--output_file, --in_place, and --centroid_file are not set; " << "no results will be saved." << endl; } arma::mat dataset = std::move(CLI::GetParam("input")); arma::mat centroids; arma::Col assignments; // Prepare and run the actual algorithm. MeanShift<> meanShift(radius, maxIterations); Timer::Start("clustering"); Log::Info << "Performing mean shift clustering..." << endl; meanShift.Cluster(dataset, assignments, centroids); Timer::Stop("clustering"); Log::Info << "Found " << centroids.n_cols << " centroids." << endl; if (radius <= 0.0) Log::Info << "Estimated radius was " << meanShift.Radius() << ".\n"; // Should we give the user the output matrix? if (CLI::HasParam("output")) CLI::GetParam>("output") = std::move(assignments); // Should we give the user the centroid matrix? if (CLI::HasParam("centroid")) CLI::GetParam("centroid") = std::move(centroids); } @endcode We can see that we have defined the basic program information in the @c PROGRAM_INFO() macro. This is, for instance, what is displayed to describe the binding if the user passed the @c --help option for a command-line program. Then, we define five parameters, three input and two output, that define the data and options that the mean shift clustering will function on. These parameters are defined with the @c PARAM macros, of which there are many. The names of these macros specify the type, whether the parameter is required, and whether the parameter is input or output. Some examples: - @c PARAM_STRING_IN() -- a string-type input parameter - @c PARAM_MATRIX_OUT() -- a matrix-type output parameter - @c PARAM_DOUBLE_IN_REQ() -- a required double-type input parameter - @c PARAM_UMATRIX_IN() -- an unsigned matrix-type input parameter - @c PARAM_MODEL_IN() -- a serializable model-type input parameter Note that each of these macros may have slightly different syntax. See the links above for further documentation. @section bindings_general General structure of code @section bindings_cli Command-line program bindings @section bindings_python Python bindings @section bindings_new Adding new binding types */