|
4 | 4 |
|
5 | 5 | <br/>
|
6 | 6 |
|
7 |
| -<div align="center"> |
8 |
| - <!-- Build status --> |
9 |
| - <a href="https://travis-ci.com/RUrlus/carma"> |
10 |
| - <img src="https://img.shields.io/travis/rurlus/carma/stable.svg?style=for-the-badge" alt="Build Status"/> |
11 |
| - </a> |
12 |
| - <!-- Coverage status --> |
13 |
| - <a href="https://coveralls.io/github/RUrlus/carma?branch=stable"> |
14 |
| - <img src="https://img.shields.io/coveralls/github/RUrlus/carma?style=for-the-badge" alt="Coveralls github" > |
15 |
| - </a> |
16 |
| - <!-- Documentation status --> |
17 |
| - <a href="https://carma.readthedocs.io/en/latest/?badge=latest"> |
18 |
| - <img src="https://readthedocs.org/projects/carma/badge/?version=latest&style=for-the-badge" alt="Documentation Status" /> |
19 |
| - </a> |
20 |
| - <!-- Release version --> |
21 |
| - <a href="https://github.com/RUrlus/carma/releases"> |
22 |
| - <img src="https://img.shields.io/github/v/release/RUrlus/carma.svg?style=for-the-badge" alt="Release Version" /> |
23 |
| - </a> |
24 |
| - <!-- License --> |
25 |
| - <a href="https://github.com/RUrlus/carma/blob/master/LICENSE"> |
26 |
| - <img src="https://img.shields.io/github/license/RUrlus/carma.svg?style=for-the-badge" alt="license"/> |
27 |
| - </a> |
28 |
| -</div> |
29 |
| - |
30 |
| -<br/> |
31 |
| - |
32 |
| -CARMA is a header only library providing conversions between Numpy arrays and Armadillo matrices. Examples and reference documentation can be found at [carma.readthedocs.io](https://carma.readthedocs.io/). |
| 7 | +<p align="center"> |
| 8 | + A C++ header only library providing conversions between Numpy arrays and Armadillo matrices. |
| 9 | +</p> |
| 10 | +<p align="center"> |
| 11 | + | |
| 12 | + <a href="https://carma.readthedocs.io/en/latest/">Documentation</a> |
| 13 | + | |
| 14 | +</p> |
| 15 | + |
| 16 | +[](https://github.com/RUrlus/carma/actions/workflows/linux.yml) |
| 17 | +[](https://github.com/RUrlus/carma/actions/workflows/macos.yml) |
| 18 | +[](https://github.com/RUrlus/carma/actions/workflows/windows.yml) |
| 19 | +[](https://coveralls.io/github/RUrlus/carma?branch=master) |
| 20 | +[](https://carma.readthedocs.io/en/latest/?badge=latest) |
| 21 | +[](https://github.com/RUrlus/carma/blob/stable/LICENSE) |
| 22 | +[](https://github.com/RUrlus/carma/releases) |
33 | 23 |
|
34 | 24 | ## Introduction
|
35 | 25 |
|
36 | 26 | CARMA provides fast bidirectional conversions between [Numpy](https://numpy.org) arrays and [Armadillo](http://arma.sourceforge.net/docs.html) matrices, vectors and cubes, much like [RcppArmadillo](https://github.com/RcppCore/RcppArmadillo) does for R and Armadillo.
|
37 | 27 |
|
38 |
| -The library relies heavily on the impressive [pybind11](https://pybind11.readthedocs.io/en/stable/intro.html) library and is largely inspired by their Eigen conversion albeit with a less conservative approach to memory management. |
39 |
| -For details on pybind11 and Armadillo refer to their respective documentation. |
| 28 | +The library extends the impressive [pybind11](https://pybind11.readthedocs.io/en/stable/intro.html) library with support for Armadillo. |
| 29 | +For details on Pybind11 and Armadillo refer to their respective documentation [1](https://pybind11.readthedocs.io/en/stable/intro.html), [2](http://arma.sourceforge.net/docs.html). |
40 | 30 |
|
41 |
| -### Installation |
| 31 | +## Installation |
| 32 | +CARMA is a header only library that relies on two other header only libraries, Armadillo and Pybind11. |
42 | 33 |
|
43 |
| -Carma is a header only library that relies on two other header only libraries, Armadillo and Pybind11. |
44 |
| -Both libraries are linked as submodule in the `third_party` directory. |
| 34 | +CARMA can be integrated in a CMake build using `ADD_SUBDIRECTORY(<path_to_carma>)` which provides an interface library target `carma` |
| 35 | +that has been linked with Python, Numpy, Pybind11 and Armadillo. |
| 36 | +**Note, at the time of writing CARMA requires a forked version of Armadillo that |
| 37 | +uses Numpy's allocator and deallocator.** This fork is provided as an interface libary target |
| 38 | +`carma_armadillo`. |
45 | 39 |
|
46 |
| -### Considerations |
| 40 | +To link with CARMA: |
| 41 | +```cmake |
| 42 | +ADD_SUBDIRECTORY(extern/carma) |
| 43 | +TARGET_LINK_LIBRARIES(<your_target> PRIVATE carma) |
| 44 | +``` |
| 45 | +CARMA and Armadillo can then be included using: |
| 46 | +```C++ |
| 47 | +#include <carma> |
| 48 | +#include <armadillo> |
| 49 | +``` |
47 | 50 |
|
48 |
| -In order to achieve fast conversions the default behaviour is avoid copying both from and to Numpy whenever possible and reasonable. |
49 |
| -This allows very low overhead conversions but it impacts memory safety and requires user vigilance. |
| 51 | +CARMA provides a number of configurations that can be set in the `carma_config.cmake` file at the root of the directory or passed to CMake, see [Configuration](https://carma.readthedocs.io/en/stable/configuration.html) and [Build configuration](https://carma.readthedocs.io/en/stable/building.html) documentation sections for details. |
50 | 52 |
|
51 |
| -A second consideration is memory layout. Armadillo is optimised for column-major (Fortran order) memory whereas Numpy defaults to row-major (C order). |
52 |
| -The default behaviour is to automatically convert, read copy, C-order arrays to F-order arrays upon conversion to Armadillo. Users should note that the library will not convert back to C-order when returning, this has consequences for matrices and cubes. |
| 53 | +## Requirements |
53 | 54 |
|
54 |
| -For details see the documentation section Memory Management. |
| 55 | +CARMA v0.5 requires a compiler with support for C++14 and supports: |
55 | 56 |
|
56 |
| -### Examples |
| 57 | +* Python 3.6 -- 3.9 |
| 58 | +* Numpy >= 1.14 |
| 59 | +* Pybind11 v2.6.0 -- v2.6.2 |
| 60 | +* Armadillo 10.4.x -- 10.5.x |
57 | 61 |
|
58 |
| -On a high level Carma provides three ways to work Numpy arrays in Armadillo: |
| 62 | +**Note, at the time of writing CARMA requires a forked version of Armadillo that |
| 63 | +uses Numpy's allocator and deallocator.** |
| 64 | +The forked version is shipped with the library and provided at build time. |
| 65 | +For details see the [Build configuration](https://carma.readthedocs.io/en/stable/building.html). |
59 | 66 |
|
60 |
| -#### Manual conversion |
| 67 | +### Considerations |
61 | 68 |
|
| 69 | +In order to achieve fast conversions the default behaviour is avoid copying both from and to Numpy whenever possible and reasonable. |
| 70 | +This allows very low overhead conversions but it impacts memory safety and requires user vigilance. |
62 | 71 |
|
63 |
| -The easiest way to use Carma is manual conversion, it gives you the most control over when to copy or not. |
64 |
| -You pass a Numpy array as an argument and/or as the return type and call the respective conversion function. |
| 72 | +If you intend to return the memory of the input array back as another array, you must make sure to either copy or steal the memory on the conversion in or copy the memory out. |
| 73 | +If you don't the memory will be aliased by the two Numpy arrays and bad things will happen. |
65 | 74 |
|
66 |
| -```cpp |
67 |
| -#include <armadillo> |
68 |
| -#include <carma/carma.h> |
69 |
| -#include <pybind11/pybind11.h> |
70 |
| -#include <pybind11/numpy.h> |
| 75 | +A second consideration is memory layout. Armadillo is optimised for column-major (Fortran order) memory whereas Numpy defaults to row-major (C order). |
| 76 | +The default behaviour is to automatically convert, read copy, C-order arrays to F-order arrays upon conversion to Armadillo. Users should note that the library will not convert back to C-order when returning. |
71 | 77 |
|
72 |
| -py::array_t<double> manual_example(py::array_t<double> & arr) { |
73 |
| - // convert to armadillo matrix without copying. |
74 |
| - arma::Mat<double> mat = carma::arr_to_mat<double>(arr); |
| 78 | +For details see the documentation section [Memory Management](https://carma.readthedocs.io/en/latest/memory_management.html). |
75 | 79 |
|
76 |
| - // normally you do something useful here ... |
77 |
| - arma::Mat<double> result = arma::Mat<double>( |
78 |
| - arr.shape(0), |
79 |
| - arr.shape(1), |
80 |
| - arma::fill::randu |
81 |
| - ); |
| 80 | +### Example |
82 | 81 |
|
83 |
| - // convert to Numpy array and return |
84 |
| - return carma::mat_to_arr(result); |
85 |
| -} |
86 |
| -``` |
| 82 | +On a high level CARMA provides two ways to work Numpy arrays in Armadillo: |
| 83 | +Automatic conversion saves a bit on code but provides less flexibility with |
| 84 | +regards to when to copy and when not. |
| 85 | +Manual conversion should be used when you need fine grained control. |
87 | 86 |
|
88 |
| -#### Update array |
| 87 | +Combining the two; we use automatic conversion on the conversion in and manual when |
| 88 | +creating the tuple for the way out. |
89 | 89 |
|
90 | 90 | ```cpp
|
| 91 | + |
| 92 | +#include <carma> |
91 | 93 | #include <armadillo>
|
92 |
| -#include <carma/carma.h> |
93 | 94 | #include <pybind11/pybind11.h>
|
94 | 95 | #include <pybind11/numpy.h>
|
| 96 | +#include <pybind11/pytypes.h> |
95 | 97 |
|
96 |
| -void update_example(py::array_t<double> & arr) { |
97 |
| - // convert to armadillo matrix without copying. |
98 |
| - arma::Mat<double> mat = carma::arr_to_mat<double>(arr); |
99 |
| -
|
100 |
| - // normally you do something useful here with mat ... |
101 |
| - mat += arma::Mat<double>(arr.shape(0), arr.shape(1), arma::fill::randu); |
102 |
| -
|
103 |
| - // update Numpy array buffer |
104 |
| - carma::update_array(mat, arr); |
105 |
| -} |
106 |
| -``` |
107 |
| - |
108 |
| -#### Automatic conversion |
| 98 | +py::tuple ols(arma::mat& X, arma::colvec& y) { |
| 99 | + // We borrow the data underlying the numpy arrays |
| 100 | + int n = X.n_rows, k = X.n_cols; |
109 | 101 |
|
110 |
| -```cpp |
111 |
| -#include <armadillo> |
112 |
| -#include <carma/carma.h> |
113 |
| -#include <pybind11/pybind11.h> |
114 |
| -#include <pybind11/numpy.h> |
| 102 | + arma::colvec coeffs = arma::solve(X, y); |
| 103 | + arma::colvec resid = y - X * coeffs; |
115 | 104 |
|
116 |
| -arma::Mat<double> automatic_example(arma::Mat<double> & mat) { |
117 |
| - // normally you do something useful here with mat ... |
118 |
| - arma::Mat<double> rand = arma::Mat<double>(mat.n_rows, mat.n_cols, arma::fill::randu); |
| 105 | + double sig2 = arma::as_scalar(arma::trans(resid) * resid / (n-k)); |
| 106 | + arma::colvec std_errs = arma::sqrt(sig2 * arma::diagvec( arma::inv(arma::trans(X)*X)) ); |
119 | 107 |
|
120 |
| - arma::Mat<double> result = mat + rand; |
121 |
| - // type caster will take care of casting `result` to a Numpy array. |
122 |
| - return result; |
| 108 | + // We take ownership of the memory from the armadillo objects and |
| 109 | + // return to python as a tuple containing two Numpy arrays. |
| 110 | + return py::make_tuple( |
| 111 | + carma::col_to_arr(coeffs), |
| 112 | + carma::col_to_arr(std_errs) |
| 113 | + ); |
123 | 114 | }
|
124 |
| -``` |
125 |
| -
|
126 |
| -The repository contains tests, examples and CMake build instructions that can be used as an reference. |
127 |
| -For manual compilation see the documentation section Usage. |
128 | 115 |
|
129 |
| -### Compatibility |
| 116 | +// adapted from https://gallery.rcpp.org/articles/fast-linear-model-with-armadillo/ |
| 117 | +``` |
130 | 118 |
|
131 |
| -`carma` has been tested with: |
| 119 | +Which can be called using: |
132 | 120 |
|
133 |
| -* armadillo-9.800.1 |
134 |
| -* pybind11-2.4.3 |
| 121 | +```python |
| 122 | +y = np.linspace(1, 100, num=100) + np.random.normal(0, 0.5, 100) |
| 123 | +X = np.hstack((np.ones(100)[:, None], np.arange(1, 101)[:, None])) |
| 124 | +coeff, std_err = carma.ols(X, y) |
| 125 | +``` |
135 | 126 |
|
136 | 127 | The repository contains tests, examples and CMake build instructions that can be used as an reference.
|
137 |
| -For manual compilation see the documentation section Usage. |
138 |
| -
|
139 |
| -**Compiler requirements through pybind11** |
140 |
| -
|
141 |
| -1. Clang/LLVM 3.3 or newer (for Apple Xcode's clang, this is 5.0.0 or newer) |
142 |
| -2. GCC 4.8 or newer |
143 |
| -3. Microsoft Visual Studio 2015 Update 3 or newer |
144 |
| -4. Intel C++ compiler 17 or newer |
145 |
| -5. Cygwin/GCC (tested on 2.5.1) |
146 | 128 |
|
147 | 129 | ### About
|
148 | 130 |
|
149 | 131 | This project was created by Ralph Urlus. Significant improvements to the project have been contributed by [Pascal H.](https://github.com/hpwxf)
|
150 | 132 |
|
151 | 133 | ### License
|
152 | 134 |
|
153 |
| -carma is provided under a Apache 2.0 license that can be found in the LICENSE file. By using, distributing, or contributing to this project, you agree to the terms and conditions of this license. |
| 135 | +CARMA is provided under a Apache 2.0 license that can be found in the LICENSE file. By using, distributing, or contributing to this project, you agree to the terms and conditions of this license. |
0 commit comments